JavaScript 閉包
閉包是將函式內部和函式外部連接起來的橋梁,JavaScript 中一個內部函式被回傳到內部使用,就會產生閉包,預編譯和作用域鏈原理幫助我們理解閉包、更好的編碼
閉包問題引入
- JavaScript 預編譯和作用域鏈知識告訴我們,一個函式在宣告時,生成一個叫 [[scope]] 的類陣列的屬性,從 0 位往后,由內到外的存盤外層函式的執行期背景關系和全域執行期背景關系,即 AO 和 GO
- 函式訪問一個宣告時,會從 [[scope]] 0 位開始尋找,即先找自身范圍內,再看外層函式,最后看全域背景關系
- 內部函式被回傳到外部使用,就會產生閉包,閉包會保持作用域鏈不釋放,可能導致記憶體泄漏或過載
閉包代碼示例
-
內部函式保持外部函式的 AO
function test1() { function test2() { var b = 2; a = 4; console.log(a); } var a = 1; return test2; } var c = 3; // (1) var test3 = test1(); test3(); // 全域預編譯 // -> GO{c: undefined, test3: undefinedd, test1: test1(){}} // -> test1.SCOPE = [ // GO{c: undefined, test3: undefinedd, test1: test1(){}} // ] // 全域執行 // 執行 (1) // -> GO{c: 3, test: undefined, test1: test1(){}} // -> test1.SCOPE = [ // GO{c: 3, test3: undefined, test1: test1(){}} // ] // 執行 test1(),函式 test1 預編譯 // -> test1.SCOPE = [ // test1_AO{a: undefined, test2: test2(){}}, // GO{c: 3, test3: undefined, test1: test1(){}} // ] // -> test2.SCOPE = [ // test1_AO{a: undefined, test2: test2(){}}, // GO{c: 3, test: undefined, test1: test1(){}} // ] // 函式 test1 執行 // -> test1.SCOPE = [ // test1_AO{a: 1, test2: test2(){}}, // GO{c: 3, test3: undefined, test1: test1(){}} // ] // -> test2.SCOPE = [ // test1_AO{a: 1, test2: test2(){}}, // GO{c: 3, test: undefined, test1: test1(){}} // ] // 函式 test1 執行完畢,test1_AO 被銷毀 // 但 test2.SCOPE 中保持了 test1_AO,這個 test1_AO 將與 test1 再無關系 // test2 被回傳到外部,用 test3 接收 // -> GO{c: 3, test3: test2(){}, test1: test1(){}} // -> test1.SCOPE = [ // GO{c: 3, test3: test2(){}, test1: test1(){}} // ] // 執行 test3(),即 test2(),test2 預編譯 // -> test2.SCOPE = [ // test2_AO{b: undefined}, // test1_AO{a: 1, test2: test2(){}}, // GO{c: 3, test3: test2(){}, test1: test1(){}} // ] // 函式 test2 預編譯 // -> test2.SCOPE = [ // test2_AO{b: 2}, // test1_AO{a: 1, test2: test2(){}}, // GO{c: 3, test3: test2(){}, test1: test1(){}} // ] // 函式 test2 執行,修改 b,a 的值,列印 4 // -> test2.SCOPE = [ // test2_AO{b: 2}, // test1_AO{a: 4, test2: test2(){}}, // GO{c: 3, test3: test2(){}, test1: test1(){}} // ] // 函式 test2 執行完畢,test2_AO 銷毀 // -> test2.SCOPE = [ // test1_AO{a: 4, test2: test2(){}}, // GO{c: 3, test3: test2(){}, test1: test1(){}} // ] -
同級內部函式共享外部函式的 AO
function cal() { var num = 10; function plus() { num ++; console.log(num); } function minus() { num --; console.log(num); } return [plus, minus]; } var c = cal(); c[0](); c[1](); c[1](); // 列印 11 10 9 // 分析程序 // GO{c: undefined, cal: cal(){}} // -> cal.SCOPE = [ // cal.AO{num: undefined, plus: plus(){}, minus: minus()}, // GO{c: undefined, cal: cal(){}} // ] // -> cal.SCOPE = [ // cal.AO{num: 10, plus: plus(){}, minus: minus()}, // GO{c: undefined, cal: cal(){}} // ] // -> GO{c: [plus, minus], cal: cal(){}} // cal.SCOPE = [ // GO{c: [plus, minus], cal: cal(){}} // ] // -> plus.SCOPE = [ // plus.AO{}, // cal.AO{num: 10, plus: plus(){}, minus: minus()}, // GO{c: [plus, minus], cal: cal(){}} // ] // -> plus.SCOPE = [ // plus.AO{}, // cal.AO{num: 11, plus: plus(){}, minus: minus()}, // GO{c: [plus, minus], cal: cal(){}} // ] // -> minus.SCOPE = [ // minus.AO{}, // cal.AO{num: 10, plus: plus(){}, minus: minus()}, // GO{c: [plus, minus], cal: cal(){}} // ] // -> minus.SCOPE = [ // minus.AO{}, // cal.AO{num: 9, plus: plus(){}, minus: minus()}, // GO{c: [plus, minus], cal: cal(){}} // ]
回圈中的閉包
-
回圈生成的內部函式,共享外部函式的 AO
回圈完畢后,內部函式裝在陣列中被拋出,其中每個函式的 [[scope]] 中都保持了 test 函式的 AO
并且這個 AO 是共享的,在這個 AO 中,保存著 i = 10;
function test() { var arr = []; var i = 0; // 放在 for 外效果一樣 for(; i < 10; i++) { arr[i] = function() { console.log(i); } } return arr; } var fnArr = test(); fnArr[0](); fnArr[1](); fnArr[2](); fnArr[3](); // 發現列印出的都是 10 // test_AO{arr: [...], i: 10} -
使用立即執行函式解決回圈閉包
立即執行函式直接將引數傳入到內部,也就是說內部的函式保持了各自外層的自執行函式的 AO,這些 AO 保存的 j 是不同的
function test() { var arr = []; var i = 0; for(; i < 10; i++) { (function(j) { arr[j] = function() { console.log(j); } })(i); } return arr; } var fnArr = test(); fnArr[0](); fnArr[1](); fnArr[2](); fnArr[3](); // 列印出 0 1 2 3
閉包的快取特性
-
閉包中內部函式共享外部函式的 AO,就可以共享其區域變數
function myClass() { var students = []; return { join: function(name) { students.push(name); }, out: function(name) { var idx = students.indexOf(name); if(idx != -1) { students.splice(idx,1); } }, show:function() { console.log(students); } } } var c = myClass(); c.join("張三"); c.show(); c.join("李四"); c.show(); c.out("李四"); c.show(); // ["張三"] // ["張三", "李四"] // ["張三"]
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/141196.html
標籤:JavaScript
