開場白
對於js來說,閉包可謂時重點和難點,前端面試應該是必問的題,對閉包的理解程度就可以看的出一個前端開發的功底。網上對閉包的文章比比皆是,但是對於初學者來說不是很友好。今天我就考慮一下初學者,畢竟我還算是吧,簡單通俗的講一下js閉包。
準備工作
要修煉js的閉包功夫,還需要一點其他功力作爲功底的。
- 功底一:js的垃圾回收知識
- 功底二:js的函數執行過程
- 功底三:js的全局變量和局部變量
大概就明白這三點就可以理解閉包了吧。對這三點也就理解個基本 不用過深的理解比較複雜的情況,如js的預處理機制,js的堆棧內存,js的變量提升什麼的。
現在我們就簡單學習一個上面三套功法。都是之學招式不學精髓。
看了一下功底三最爲好學。一句話 函數內生成的變量叫局部變量 函數外生成的變量叫全局變量 話不嚴謹應該好理解,其實後面還有兩句,函數內可以訪問全局變量,函數外訪問不了局部變量 ;太長了不太好記。用例子來說吧。
var a = 'AAAA';
function fn() {
var b = 'BBB';
console.log(a) //打印AAA
}
fn();
console.log(b) // b is not defined
上面例子就不解釋了。。。
再看一下js的垃圾回收機制:也是一句話 局部變量在函數執行完成之後自動回收 就拿上面的函數來說,當fn執行完成之後函數內創建的變量會自動回收
最麻煩的功底二來了,關於函數的執行過程,基礎的就是函數開始不執行,調用之後從上到下依次執行,編譯錯誤和return就結束,但是複雜的就是有函數的 有return的。舉個例子吧
function fn() {
var b = 'BBB';
return function () {
console.log("CCCC")
}
}
fn();
咱們來分步執行一下
- 代碼開始了走到fn(){} 這個函數,沒有執行語句跳過它。
- 走到fn() 開始執行fn這個函數 最終返會了一個函數。結束
- 函數結束 局部變量 b 被回收
再變一下型 往後加個劇本
function fn() {
var b = 'BBB';
return function () {
console.log("CCCC")
}
}
var c = fn();
c();
接上面的 返回一個函數 functio(){console.log('CCC')} 賦值給 c這個變量;(加一句:b被回收) 接着執行c() 這個函數打印出CCC(加一句 如上面return的函數內定義變量,也會被回收)
閉包開講
準備工作完成之後一個普通函數的經歷過程應該可以看的懂了吧。
那就看一個例子一步一步執行一下
/*1*/ function fn() {
/*2*/ var b = 'BBB';
/*3*/ return function () {
/*4*/ console.log(b)
/*5*/ }
/*6*/ }
/*7*/ var c = fn();
/*8*/ c();
/*9*/ c();
/*10*/ c();
函數有點長給標一個行號 ;長函數不要着急 一步一步來。
- 1-6行定義了一個函數不執行
- 第7行返回了一個函數
function () { console.log(b) } 賦值個變量c c就是該函數了
- 按照上面的垃圾回收 函數內的b變量被回收
- 第8行執行函數c 打印b,因爲b被銷燬了 所以應該是 b is not defined 結束 9行10行就不執行了
代碼複製運行 垃圾小編 不對! 看來js處理和我們想的不一樣。
那就一起來分析一下吧? 結果顯示 打印了三個 BBB,說明程序中沒有出錯,也就說b變量沒有被銷燬,再說一下就是外面可以訪問內部函數定義的變量;這就生成了閉包。閉包最核心的也就是打破了js的垃圾回收機制,讓局部變量不被銷燬。曾看到一篇文章把閉包比喻成一個揹包,可以把函數的變量放進去,當它被返回時就一起帶走了。
那咱們再走一邊上面程序,加一句話
/*1*/ function fn() {
/*2*/ var b = 'BBB';
/*3*/ return function () {
/*4*/ b = b+'B'
/*5*/ console.log(b)
/*6*/ }
/*7*/ }
/*8*/ var c = fn();
/*9*/ c();
/*10*/ c();
/*11*/ c();
- 1-7行定義一個函數
- 第8行返回了一個函數
function () { b = b+'B' console.log(b) } 賦值個變量c c就是該函數了
- 因爲返回的函數 用到fn這個函數內生成的b變量。所以就打開揹包把 b='BBB',給揹走了,就成了以下函數
var b = 'BBB' function () { b = b+'B' console.log(b) }
- 第九行執行c函數兩步① b 的值改爲爲'BBB'+'B' 爲BBBB ② 打印'BBBB'
- 第10行再執行c函數 剛纔把b的值已經改爲'BBBB'了 再加一個B 打印'BBBBB'
- ...
這就是一個簡單的閉包例子,也是很多筆試題閉包實現數字累計的方法。
有的同學就會說 定義一個全局變量,底下函數直接給賦值就好了。這裏只講閉包不講程序設計,爲何要避免全局變量。要實現一個功能for 0-100循環不是也可以直接寫100下。
知識梳理
- 閉包產生的條件:函數內部函數作爲返回值,並使用函數內的局部變量
- 閉包的表現:所用的變量不被銷燬,一直存在內存中 這就是閉包常說的 可以導致內存泄露 和 變量提升。這就是可以成爲私有屬性創建模式
應用案例
1.實現累加計數 這個例子和上面的差不多 只是有兩個return 按上面的就很好理解了
function fn() {
var init = 0;
return function () {
init = init+1
return init;
}
}
var fun = fn();
var add1 = fun();
var add2 = fun();
var add3= fun();
var add4 = fun();
console.log(add1);
console.log(add2);
console.log(add3);
console.log(add4);
2.實現函數私有屬性
1. function book() {
2. var page = 100;
3. return function () {
4. this.auther = 'wuy';
5. this.price = 200;
6. this._page = function () {
7. return page
8. }
9. }
10. }
11. var books = book();
12. var cssbook = new books();
13. console.log(cssbook._page()); //打印100
14. console.log(cssbook.auther); //打印wuy
簡單分析一下
- 1-10行 先定義一個函數book ,函數,
- 11行 函數開始運行 執行 2-9行代碼,定義一個page局部變量 返回返回值爲一個函數 3-9行 返回的函數就用到了book內定義的page 閉包形成 page 一直存在內存中。
- 第12行 返回的函數 3-9行 是一個構造函數用new關鍵字聲明成一個對象 賦值爲cssbook;因此函數內部的this指向cssbook
- 第13行 打印cssbook的_page()這個方法的返回值 就是內存中的page 100 ;這就是函數的私有屬性
- 第14行 打印cssbook的auther wuy