關於閉包的理解記錄
問題:如何從從外部讀取函式區域性變數?
解決方法:在函式的內部,再定義一個函式
function lazy_sum(arr) { var sum = function () { return arr.reduce(function (x, y) { return x + y; }); } return sum; }
var f = lazy_sum([1, 2, 3, 4, 5]);
f(); // 15
從js的作用域結構我們可以知道函式 lazy_sum可以讀取sum中的區域性變數和引數,那麼只要把sum作為返回值,我們不就可以在lazy_sum外部讀取它的內部變量了嗎!
在這個例子中,我們在函式lazy_sum
中又定義了函式sum
,並且,內部函式sum
可以引用外部函式lazy_sum
的引數和區域性變數,當lazy_sum
返回函式sum
時,相關引數和變數都儲存在返回的函式中,這種稱為“閉包(Closure)”的程式結構擁有極大的威力。
注1:返回的函式並沒有立刻執行,而是直到呼叫了f()
才執行
function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push(function() { return i * i; }); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; f1(); // 16 f2(); // 16 f3(); // 16
原因就在於返回的函式引用了變數i
,但它並非立刻執行。等到3個函式都返回時,它們所引用的變數i
已經變成了4
,因此最終結果為16(因為每個閉包的作用域鏈中都儲存著count函式的活動物件,所以它們引用的是同一個變數i,當count函式執行完後i的值是4,所以閉包中的i也是4 )
返回閉包時牢記的一點就是:返回函式不要引用任何迴圈變數,或者後續會發生變化的變數
如果一定要引用迴圈變數怎麼辦?方法是再建立一個函式,用該函式的引數繫結迴圈變數當前的值,無論該迴圈變數後續如何更改,已繫結到函式引數的值不變
function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push((function (n) { return function () { return n * n; } })(i)); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; f1(); // 1 f2(); // 4 f3(); // 9
建立一個每次迴圈都會執行一次的匿名函式:將每次迴圈時包圍函式的i值作為引數,存入匿名函式中。因為函式引數是按值傳遞的,而非引用,所以每個匿名函式中的i值 都為每此迴圈時i的值
總結:
一.什麼是閉包
簡單來說,閉包是指可以訪問另一個函式作用域變數的函式,一般是定義在外層函式中的內層函式
二.閉包的作用
1.讀取函式內部的變數
2.讓這些變數的值始終保持在記憶體中(區域性變數無法共享和長久的儲存,而全域性變數可能造成變數汙染,所以我們希望有一種機制既可以長久的儲存變數又不會造成全域性汙染。)
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 nAdd(); result(); // 1000
在這段程式碼中,result實際上就是閉包f2函式。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證明了,函式f1中的區域性變數n一直儲存在記憶體中,並沒有在f1呼叫後被自動清除。
為什麼會這樣呢?原因就在於f1是f2的父函式,而f2被賦給了一個全域性變數,這導致f2始終在記憶體中,而f2的存在依賴於f1,因此f1也始終在記憶體中,不會在呼叫結束後,被垃圾回收機制(garbage collection)回收。
三.使用閉包的注意點
1)由於閉包會使得函式中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,在IE中可能導致記憶體洩露。解決方法是,在退出函式之前,將不使用的區域性變數全部刪除。
記憶體洩漏:程式中己動態分配的堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。
2)閉包會在父函式外部,改變父函式內部變數的值。所以,如果你把父函式當作物件(object)使用,把閉包當作它的公用方法(Public Method),把內部變數當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函式內部變數的值。
四.應用場景
把多引數的函式變成單引數的函式
function make_pow(n) { return function (x) { return Math.pow(x, n); } } // 建立兩個新函式: var pow2 = make_pow(2); var pow3 = make_pow(3); console.log(pow2(5)); // 25 console.log(pow3(7)); // 343
參考連結:1.http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
2.https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/00143449934543461c9d5dfeeb848f5b72bd012e1113d15000
3.https://github.com/mqyqingfeng/Blog/issues/9
4.https://www.jb51.net/article/126565.htm
5.https://blog.csdn.net/u012028371/article/details/79609428