Javascript 闭包 - 变量范围问题
我正在阅读 Mozilla 开发人员的关于闭包的网站,我注意到在他们的常见错误示例中,他们有这样的代码:
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
他们
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
说对于 onFocus 事件,代码只会显示最后一项的帮助,因为所有分配给 onFocus 事件的匿名函数在“item”变量周围有一个闭包,这是有意义的,因为在 JavaScript 中变量没有块作用域。解决方案是使用“let item = ...”来代替,因为这样它就有了块作用域。
但是,我想知道为什么你不能在 for 循环上方声明“var item”?然后它具有 setupHelp() 的范围,并且每次迭代都为它分配一个不同的值,然后该值将被捕获为闭包中的当前值......对吗?
I'm reading the Mozilla developer's site on closures, and I noticed in their example for common mistakes, they had this code:
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
and
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
and they said that for the onFocus event, the code would only show help for the last item because all of the anonymous functions assigned to the onFocus event have a closure around the 'item' variable, which makes sense because in JavaScript variables do not have block scope. The solution was to use 'let item = ...' instead, for then it has block scope.
However, what I wonder is why couldn't you declare 'var item' right above the for loop? Then it has the scope of setupHelp(), and each iteration you are assigning it a different value, which would then be captured as its current value in the closure... right?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
这是因为在评估
item.help
时,循环将完全完成。相反,您可以使用闭包来做到这一点:JavaScript 没有块作用域,但它有函数作用域。通过创建闭包,我们永久捕获对
helpText[i]
的引用。Its because at the time
item.help
is evaluated, the loop would have completed in its entirety. Instead, you can do this with a closure:JavaScript doesn't have block scope but it does have function-scope. By creating a closure, we are capturing the reference to
helpText[i]
permanently.闭包是一个函数以及该函数的作用域环境。
它有助于理解 Javascript 在这种情况下如何实现作用域。事实上,它只是一系列嵌套的字典。考虑以下代码:
当程序开始运行时,您有一个作用域字典,即全局字典,其中可能定义了许多内容:
假设您调用 myFunc,它有一个局部变量 x。为此函数的执行创建一个新作用域。该函数的局部作用域如下所示:
它还包含对其父作用域的引用。所以函数的整个作用域如下所示:
这允许 myFunc 修改 global1。在 Javascript 中,每当您尝试为变量赋值时,它都会首先检查变量名称的本地范围。如果没有找到,它会检查父作用域以及该作用域的父作用域等,直到找到该变量。
闭包实际上是一个函数加上一个指向该函数作用域的指针(其中包含指向其父作用域的指针,等等)。因此,在您的示例中,在
for
循环执行完毕后,作用域可能如下所示:您创建的每个闭包都将指向此单个作用域对象。如果我们要列出您创建的每个闭包,它看起来会像这样:
当这些函数中的任何一个执行时,它都会使用它传递的作用域对象 - 在这种情况下,每个函数都有相同的作用域对象!每个变量都会查看相同的
item
变量并看到相同的值,这是for
循环设置的最后一个值。要回答您的问题,无论您在
for
循环上方还是在循环内部添加var item
都没有关系。由于for
循环不会创建自己的作用域,因此item
将存储在当前函数的作用域字典中,即setupHelpScope
。在for
循环内生成的封装将始终指向setupHelpScope
。一些重要的注意事项:
for
循环没有自己的作用域 - 它们只是使用封闭函数的作用域。对于if
、while
、switch
等也是如此。另一方面,如果这是 C#,则新的作用域对象将是为每个循环创建,每个闭包将包含一个指向其自己唯一范围的指针。进一步阅读:
A closure is a function and the scoped environment of that function.
It helps to understand how Javascript implements scope in this case. It is, in fact, just a series of nested dictionaries. Consider this code:
When the program starts running, you have a single scope dictionary, the global dictionary, which might have a number of things defined in it:
Say you call myFunc, which has a local variable x. A new scope is created for this function's execution. The function's local scope looks like this:
It also contains a reference to its parent scope. So the entire scope of the function looks like this:
This allows myFunc to modify global1. In Javascript, whenever you attempt to assign a value to a variable, it first checks the local scope for the variable name. If it isn't found, it checks the parentScope, and that scope's parentScope, etc. until the variable is found.
A closure is literally a function plus a pointer to that function's scope (which contains a pointer to its parent scope, and so on). So, in your example, after the
for
loop has finished executing, the scope might look like this:Every closure you create will point to this single scope object. If we were to list every closure that you created, it would look something like this:
When any of these functions executes, it uses the scope object that it was passed - in this case, it's the same scope object for each function! Each one will look at the same
item
variable and see the same value, which is the last one set by yourfor
loop.To answer your question, it doesn't matter whether you add
var item
above thefor
loop or inside it. Becausefor
loops do not create their own scope,item
will be stored in the current function's scope dictionary, which issetupHelpScope
. Enclosures generated inside thefor
loop will always point tosetupHelpScope
.Some important notes:
for
loops do not have their own scope - they just use the enclosing function's scope. This is also true ofif
,while
,switch
, etc. If this were C#, on the other hand, a new scope object would be created for each loop, and each closure would contain a pointer to its own unique scope.anonymousFunction1
modifies a variable in its scope, it modifies that variable for the other anonymous functions. This can lead to some really bizarre interactions.Further reading:
我意识到最初的问题已有五年历史了...但是您也可以将不同/特殊范围绑定到您分配给每个元素的回调函数:
如果您想要单行(或关闭)到它):
或者更好的是,您可以使用 EcmaScript 5.1 的
array.prototype.forEach
,它可以为您解决范围问题。I realize the original question is five years old... But you could also just bind a different/special scope to the callback function you assign to each element:
If you want a one-liner (or close to it):
Or better yet, you can use EcmaScript 5.1's
array.prototype.forEach
, which fixes the scope problem for you.新作用域仅在
function
块中创建(以及with
,但不要使用它)。像for
这样的循环不会创建新的作用域。因此,即使您在循环外声明变量,也会遇到完全相同的问题。
New scopes are only created in
function
blocks (andwith
, but don't use that). Loops likefor
don't create new scopes.So even if you declared the variable outside the loop, you would run into the exact same problem.
即使它是在 for 循环之外声明的,每个匿名函数仍然会引用同一个变量,因此在循环之后,它们仍然会指向 item 的最终值。
Even if it's declared outside of the for loop, each of the anonymous functions will still be referring to the same variable, so after the loop, they'll all still point to the final value of item.