爲了解決閉包的問題,我們必須瞭解變量作用域在JavaScript中的工作方式。讓我們看一個基本的功能和相關的問題開始:
function uniqueInteger(){
var counter = 0;
return ++counter;
}
console.log(uniqueInteger()); // returns '1'
console.log(counter); // undefined
此函數聲明和值0分配給變量counter
,然後返回遞增該值。正如我們所看到的,當函數正在執行時,變量計數器可以在函數中訪問。但是,一旦函數返回,變量就不再被定義,因爲它是uniqueInteger()
函數範圍的一部分。我們也可以說counter
變量是private;只有功能uniqueInteger()
可以訪問它。
但是如果我們想「記住」該變量的值,以便調用此函數,下一次,counter
從1開始關閉,而不是0。一個辦法是聲明變量counter
之外的功能,但那麼它將不再是私人的,其他功能可以改變計數器變量。
爲了解決這個問題,我們需要看看函數是如何工作的。當一個函數被調用時(如上所示),其變量返回後不再存在。但是,這個功能的範圍內,任何嵌套函數將仍然能夠訪問它的父功能的「私有」變量:
function uniqueInteger(){
var counter = 0;
// an example of nested function accessing the counter variable:
return (function() { return ++counter })();
}
console.log(uniqueInteger()); // returns '1'
console.log(counter); // undefined
*請注意,調用與括號()一致來定義的函數的數量。嵌套函數是自調用,與外部函數被調用,因此,相同的功能可以寫成行console.log(uniqueInteger());
:
function uniqueInteger(){
var counter = 0;
// an example of nested function accessing the counter variable:
return function() { return ++counter };
}
console.log(uniqueInteger()()); // returns '1'
console.log(counter); // undefined
正如我們所看到的,嵌套函數仍具有訪問變量計數器和uniqueInteger()
函數仍然返回「1」。雖然在uniqueInteger()
返回(我們將在稍後解決該問題)之後,計數器變量仍然消失,但嵌套函數訪問counter
的事實使它能夠對此變量執行操作,然後返回結果。每當調用uniqueInteger()
時,將存在相同的「範圍鏈」。非常簡單地說,這被稱爲詞彙範圍。
現在讓我們來討論一下變量計數器在函數返回後消失的問題。這裏發生的是JavaScript的一個特性叫做垃圾回收。當函數及其變量不再使用時,它們會被「拋出」。但是,如果存在對該函數返回值的引用(例如,如果該函數被分配給外部變量),則根據我們所討論的「範圍鏈」將其與其中的任何變量一起「記住」以上:
function uniqueInteger(){
var counter = 0;
return function() { return ++counter };
}
var uniqueInt = uniqueInteger();
console.log(uniqueInt()); // returns '1'
console.log(uniqueInt()); // returns '2'
console.log(counter) // still "undefined"
那麼發生了什麼?因爲我們「保存」了外部變量內嵌套函數的返回值,所以變量counter
沒有被垃圾收集,並且下一次調用uniqueInt()
時,計數器仍然等於1.我們也可以說它的狀態已保存。我們實現了我們的目標:我們創建了一個函數,它記住了它在調用之間的變量,並且在外面定義了它的變量,並保持它們是私人的。
我們可以重寫該功能如上函數定義表達式:
var uniqueInteger = (function(){
var counter = 0;
return function() { return ++counter };
})();
console.log(uniqueInteger()); // returns '1'
console.log(uniqueInteger()); // returns '2'
console.log(counter) // still "undefined"
注意,仍然有兩個功能,並且因此兩個對應的調用。只有這次外部函數是自調用的,以便將其返回值(不僅是函數本身)保存到變量uniqueInteger
。否則,變量uniqueInteger
的返回值將爲function() { return ++counter; }
。上面描述的技術基本上是封閉的:在函數內部使用函數(*或對象)來操作內部值,這些內部值在調用之間保存它們的狀態,同時保持其私有性。
*您也可以返回具備的功能是在外面函數的值進行操作對象:
var uniqueInteger = (function(){
var counter = 0;
return {
value: function() { return counter },
count: function() { return ++counter },
reset: function() { counter = 0; return counter;}
};
})();
console.log(uniqueInteger.value()); // 0
uniqueInteger.count();
console.log(uniqueInteger.value()); // 1
uniqueInteger.reset()
console.log(uniqueInteger.value()); // again 0
這種模式可以讓你有與自己的私人變量運行一個自包含的函數對象從外部排除名稱衝突或惡意篡改的風險。
我也很難掌握閉包,但如果你繼續閱讀文獻和SO答案,你最終會得到它。只需繼續玩你的代碼:)
所以我有'x'兩個閉包,但兩種方法共享變量,對不對?我已經讀過,過度關閉對性能不利,是否有辦法在沒有關閉的情況下做同樣的事情? – vtortola