2011-09-09 63 views
0

我一直在閱讀有關關閉和javascript,我以爲我聽錯了,直到我嘗試這樣做:準確的封閉是什麼?

var Object5 = function (param) { 
    var x = 0; 

    var squareBrace = function() { 
     return '[' + param + x + ']'; 
    }; 

    this.toString = function() { 
     x = x + 1; 
     return squareBrace(); 
    }; 
}; 

然後我跑這個代碼:

var counter = new Object5("Counter: "); 
print("Has x:" + ('x' in counter)); 
print("Has f:" + ('f' in counter)); 
print("Can access x:" + (!!counter.x)); 
print("Can Invoke f:" + (!!counter.f)); 
print(counter.toString()); 
print(counter.toString()); 
print(counter.toString()); 
print(counter.toString()); 
print(counter.toString()); 
print(counter.toString()); 

這就是我得到了什麼:

Has x:false 
Has f:false 
Can access x:false 
Can Invoke f:false 
[Counter: 1] 
[Counter: 2] 
[Counter: 3] 
[Counter: 4] 
[Counter: 5] 
[Counter: 6] 

我想我會得到一個「類型錯誤」,因爲「x」和「F」會被「不確定」,但後來我得到它的工作。我認爲封閉是爲了實現這種行爲,'x'和'y'是'私人',沒有封閉的成員將被遺忘。

顯然我弄錯了,或者我錯過了重要的東西在這裏。

請有人告訴我什麼是當時的關閉和爲什麼這項工作?

謝謝。

回答

3

關閉是一個作用域技術。這是將一個範圍中定義的參數拉入另一個範圍的一種方法。當你做

var x = 1; 

var f = function(){ 
    console.log(x); // you've just 'closed in' the variable x. 
}; 

f(); 

x會在即使它不是在函數聲明的功能可用。

在您的具體實例中,2個變量是關閉的:paramx。它們與您定義的功能範圍相關聯。當你執行toString時,你正在增加關閉x。當toString執行squareBrace時,該方法同時使用xparam。因此,對於每種方法(它也在對象本身的範圍內),變量爲x有2個閉包。

+0

所以我有'x'兩個閉包,但兩種方法共享變量,對不對?我已經讀過,過度關閉對性能不利,是否有辦法在沒有關閉的情況下做同樣的事情? – vtortola

6

爲了解決閉包的問題,​​我們必須瞭解變量作用域在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答案,你最終會得到它。只需繼續玩你的代碼:)