经过大量的阅读和黑客攻击后,我觉得我终于开始理解JavaScript闭包及其用途了。然而,我读过的一些资源以某种方式措辞,似乎可能与我略有矛盾。或者,也许我只是在读它们太多了。
来自helephant.com(link)的精彩文章指出:
当嵌套在另一个函数中的函数从其父作用域访问变量时,将创建闭包。
和...
闭包实际上是在外部函数退出时创建的,而不是在创建内部函数时创建的。
我在给出的例子的背景下理解这两点。
但是,John Resig的网站(link)提供了一个更基本的教程,指出下面的代码块中有一个闭包。
var num = 10;
function addNum(myNum){
return num + myNum;
}
addNum(5);
我见过的闭包的任何有用的示例都会返回对内部函数的引用,并在以后执行某些操作。所以这个例子似乎毫无意义,但无论如何,让我们试着接受并理解它仍然是一个封闭,无论如何。然而,当我尝试整合helephant的概念(在外部函数退出之前不会创建闭包)并且稍微破解它时,我想出了这个:
function outerFunction() {
var num = 10;
function addNum(myNum){
return num + myNum;
}
alert(addNum(0));
num = 5;
}
outerFunction();
现在,根据Resig的说法,addNum
创建了一个闭包。根据helephant的说法,在outerFunction
返回之前不会创建闭包。但是......闭包(如果它确实是一个闭包)在num
退出时使用之前的<{1}}值。矛盾?
不可否认,由于我在outerFunction
退出之前调用 addNum
,因此使用outerFunction
的当前值似乎是合乎逻辑的。但这让我质疑Resig的说法,即他提出的简单例子确实是一个封闭。但是......我是谁来质问Resig?当然我误解了什么?
为了使这更符合Q&amp; A格式,我的问题归结为:
(1)Resig的例子(以及我的扩展进一步向下)是一个闭包吗?
(2)如果是,为什么在外部范围返回之前使用封闭变量的值?
答案 0 :(得分:2)
所以这并不像对语义的争论那么矛盾。看起来你对这种情况有了很好的了解,你对闭包工作方式的描述似乎是准确的。
来自its Wikipedia entry(最初来自Sussman和Steele。“Scheme:扩展lambda演算的翻译”。)
在计算机科学中,闭包(也是词法闭包或函数闭包)是函数的函数或引用以及引用环境 - 存储对每个非局部变量(也称为自由变量)的引用的表那个功能。闭包 - 与普通函数指针不同 - 允许函数访问那些非局部变量,即使在其直接词法范围之外调用它也是如此。
因此,闭包在技术上只是一个具有引用环境的函数(在javascript中引用外部函数作用域)。但是它的特殊之处在于可以从另一个范围调用它。
从技术上讲,Resig的例子就是封闭。它是一个引用外部环境的函数。它的外部环境恰好是全球范围,但它仍然有一个。但与其他功能相比,它没有区别/特殊,直到它通过。
最后调用这些示例闭包没有错。但是,与通用函数相比,它们是有用的,您可能希望将它传递出调用上下文。
答案 1 :(得分:1)
window
实际上有一个功能范围。
window
存在函数范围(var bob = "Bob";
)和对象属性(window.bob = "Bob";
)。
在典型的使用中,它们是相同的,但实际上,它们不是。
window.bob = "Bob"
delete window.bob;
window.bob; // undefined
var bob = "Bob"
delete window.bob;
bob; // "Bob"
因此,只要您运行的作用域可以访问外部函数的作用域,就会发生闭包。
但是 我不会说window
提供关闭的原因只是window
可以全局访问。
也就是说,网站上的每段代码都可以访问window
的功能范围
因此,它并非真正“封闭”,因为您没有阻止访问它。
你可以从中得到的结论是,依赖于全局范围的例子与构建闭包的技术相同,因此是开始掌握它们的最简单,最通用的方法......
...但是,因为这些值不会被外界所隐藏,事后(通过外部函数返回),没有真正的闭包发生。
答案 2 :(得分:1)
所以闭包证明的一点是内部函数可以挂在外部函数的变量上,甚至在外部函数的范围之外。
内部函数通过Javascript的prototypical inheritance继承外部函数的变量和值的副本,并且在外部函数的作用域中尚未评估第二个变量赋值,因此内部函数将继承当前的副本分配值。
很好地展示了这种行为:
function outer()
{
var test = "hello";
var blah = function() { window.alert(test); };
blah();
test = "not hello";
blah();
}
outer();
答案 3 :(得分:0)
@DaveJohnson,你好。
让我试着解释为什么The closure is actually created when the outer function exits, not when the inner function is created
是一个真实的陈述。
在某个范围(函数)中定义新变量时,将在此范围内创建此变量并lives
。当函数返回时,正常情况是不保留对该变量的引用,因此它不再存在。这将是“垃圾收集”。但是,如果某个内部函数(范围)使用此变量并在外部作用域返回后保持对它的引用,则会创建一个闭包。据说The closure is actually created when the outer function exits, not when the inner function is created.
在您的示例中:
function outerFunction() {
var num = 10;
function addNum(myNum){
return num + myNum;
}
alert(addNum(0));
num = 5;
}
outerFunction();
num函数在调用outerFunction时创建,并在函数返回时完全释放。为什么?因为之后没有对该对象的引用。实际上,内部函数addNum也是在该范围内创建的,并且在outerFunction返回后它不会存在。所以我在这里看不到真正的关闭。
修改: 我们在此处看到的仅仅是SCOPING
。是 - 在addNum函数内部,引用是MADE到外部作用域的变量,但该变量不是enclosed
。如果只是在代码中的那个位置,你将addNum函数作为事件处理程序分配给按钮单击事件,它将是enclosed
。这样,函数本身作为对象将在返回outerFunction后继续存在,因为在按钮单击事件中将存在对该函数的引用,并且在这种情况下也将存在对num
变量的引用。
如果以这种方式创建addNum函数:
...
window.addNum = function(myNum) {
return num + myNum;
}
...
那将是完全不同的情况。这里创建了一个闭包,但可以说,当外部函数返回时会发生。为什么?因为那是num
变量应该“垃圾收集”的时刻,这不会发生,因为仍然存在对该对象的引用。
关闭什么是一个非常好的解释。想象一下你的外在范围是一种婚姻。这个范围实际上是一个与你的家人住在一起的房子 - 你有一个妻子,两个孩子,家具等。它们都是在婚姻范围内创造的财产和功能。当你被抛弃时,你会被踢出你的房子(函数'婚姻'已经返回),但你仍然保留对婚姻中创建的对象的引用,这些对象将是你的两个孩子:)。至于你的妻子 - 她将不再存在于你自己的“范围”中,因为妻子和你的家具将不再是你的妻子:)
<强>更新强>
我首先要向您解释的是,示例中的'The closure is actually created when the outer function exits, not when the inner function is created
'适用于addNum函数,但不适用于outerFunction,因为在outerFunction退出后没有创建闭包。
更新2 - 这不是关于一个值,而是一个引用
// man is not the value "Jonh Resig"
// it's a pointer to SOME 'man', existing in the memory
var man = "John Resig";
function haveSex(woman) {
return man + woman; // creating a baby;
}
haveSex("Some Girl"); // baby of "Some Girl" and Resig
man = "Douglas Crockford"; // changing `man` reference to point to another object
haveSex("Some Girl"); // baby of "Some Girl" and Crockford
答案 4 :(得分:0)
当helephant写下这条评论时,他正在提出具体观点。使用我自己的例子来表达同样的观点,考虑下面的代码示例 - 我们循环创建10个函数,将它们放在一个数组中,然后遍历数组并调用它们:
function getFunctions() {
var functions = [];
for (var i=0; i < 10; i++) {
functions.push(function() { window.alert(i); });
}
return functions;
}
functionArray = getFunctions();
for (var j=0; j < functionArray.length; j++) {
var f = functionArray[j];
f();
}
Helephant警告你不要混淆:这会提醒数字“10”十次。它没有警告1,2,3,...... 10.但他的术语和解释并不完全准确。
执行function() { window.alert(i); }
时,系统会创建一个闭包。正如本麦考密克所说,封闭包括两个指针。一个指向函数定义(函数和函数体的参数)。另一个指向环境。
在我们的示例中,所有10个闭包都指向相同的环境。
再次 - 在技术上非常精确,有10个闭包,但只有一个环境。
此环境是i
绑定到“10”的位置。
这应该具有直观意义。 var i
只执行一次,因此只有i
的一个实例。 function() { window.alert(i); }
执行了10次,因此有10个闭包。但是所有10个闭包都指向相同的环境。