2011-09-09 39 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答案,你最终会得到它。只需继续玩你的代码:)