试图掌握两者之间的差异,我正在尝试不同的例子以确保我理解正确。
我已经在Stack Overflow上看过这个问题了: What's the difference between using "let" and "var" to declare a variable?
在我偶然发现这段代码之前,它似乎有意义: http://jsbin.com/xepovisesi/edit?html,output
const buttons = document.getElementsByTagName("button");
for(var i = 0; i < buttons.length; i++) {
const button = buttons[i];
button.addEventListener("click", function() {
alert("Button " + i + " Pressed");
});
}
<button />
<button />
<button />
<button />
如果您将第一个for for循环语句更改为let,则该功能将按预期工作...它会警告按下了哪个按钮。然而var每次都会警告“按下按钮10”。它为什么这样做?
谢谢, 詹姆斯。
答案 0 :(得分:4)
在相关问题的接受答案的第一段中对此进行了解释:
区别在于范围界定。 var的范围限定为最近的功能块 并且将其限定为最近的封闭块(如果是,则两者都是全局的) 在任何块之外),它可以小于功能块。
var
在for
语句块之外的范围内创建一个变量。 let
的等效代码如下所示:
let i = 0;
for(i = 0; i < buttons.length; i++) {
const button = buttons[i];
button.addEventListener("click", function() {
alert("Button " + i + " Pressed");
});
}
每个按钮的事件监听器都绑定到i
的单个实例,在for
循环结束后其值为10,当您按下每个按钮时,您会看到它。
此代码
for(let i = 0; i < buttons.length; i++) {
const button = buttons[i];
button.addEventListener("click", function() {
alert("Button " + i + " Pressed");
});
}
在i
语句块的范围内有for
。这意味着,对于每次迭代,都会在事件处理程序中创建并绑定i
的新实例,因此每个处理程序显示i
的不同值。
如果没有let
,您可以通过引入函数来创建范围。在let
之前编写此类代码的常用方法是
const buttons = document.getElementsByTagName("button");
for(var i = 0; i < buttons.length; i++) {
addButtonListener(i);
}
function addButtonListener(i) {
const button = buttons[i];
button.addEventListener("click", function() {
alert("Button " + i + " Pressed");
});
}
答案 1 :(得分:3)
这不是很明显,需要明确指出。
原因是let
语句中的for(...)
创建了一个新的&#34;绑定&#34;在每次迭代。这是因为标准是这样说的,但在其他情况下如果不改变let
的含义则可能会有所不同。
例如,在Common Lisp中,如果(dotimes ...)
表单在每次迭代时都会创建一个新绑定,则它是未指定的(取决于实现)。
换句话说,Javascript代码如:
for (let i=0; i<10; i++) {
...
}
相当于
{
let __hidden__ = 0;
for (__hidden__ = 0; __hidden__ < 10; __hidden__++) {
let i = __hidden__;
...
}
}
它和
不一样{
let i = 0;
for (i = 0; i<10; i++) {
...
}
}
我个人认为这确实是最有用的语义(每次迭代时都是一个新的绑定),因为在捕获的情况下重用相同的绑定可能会令人惊讶。当你需要它时,它也更容易实现其他语义(第二个剪切比第一个更简单和更短)。
重用相同的绑定,例如Python 3对于理解中的变量所做的事情:
[(lambda : i) for i in range(10)][3]() # ==> 9
最后在我看来,最糟糕的选择是让它依赖于实现...... Common Lisp令人惊讶地做了......
(let ((L (list)))
(dotimes (i 10)
(push (lambda () i) L))
;; May be the following will print 10 times 10, or may
;; be the numbers from 9 to 0
(dolist (f L)
(print (funcall f))))
答案 2 :(得分:2)
区别在于范围。在此示例中,您将创建一个javascript闭包(通过在for循环中包含事件侦听器)。在函数定义时,Javascript不会将值写入函数的词法范围,而是在函数执行时查找值。当您使用 var 时,它将在其内部函数的范围内定义变量i
。在这种情况下,全球范围。因此,每个循环迭代引用相同的i
变量,该变量相当于瞬间增加到10,并且变量继续存在于for循环之后。因此,当事件侦听器触发时,它会引用全局范围内的i
变量,其值仍为10.
另一方面,使用让创建i
的LOCAL副本,该副本的范围限定为其各自的循环迭代。当事件监听器被触发时,它会检查它自己的范围,看看它显然找不到的变量i
,然后它向外移动,下一步是for loop
的范围。它找到i
的本地副本,它保留了循环特定迭代的值 - 因此,您可以为每个按下的按钮获得正确的警报消息。
让将变量范围扩展到定义它的块
在搜索变量的定义时,闭包中引用的变量向外通过作用域链,在全局范围内停止。
希望这有帮助!
答案 3 :(得分:0)
在按钮事件处理程序中捕获变量'i'。
1)如果您没有'let'可用,您可以通过在IIFE中查找新变量来解决此问题,如下所示:
const button = buttons[i];
(function() {
var j = i;
button.addEventListener("click", function() {
alert("Button " + j + " Pressed");
});
})()
2)或者,如果您有“让”可用,请执行:
const button = buttons[i];
let j = i;
button.addEventListener("click", function() {
alert("Button " + j + " Pressed");
});
答案 4 :(得分:0)
原因是因为var
关键字是范围顶部的hoisted,其中let
是块范围(最近的大括号)。让我通过向您展示JavaScript运行时如何对代码进行伪解释来解释。
var
... function () {
for(var i = 0; i < buttons.length; i++) {
var button = buttons[i];
[...]
}
}
function () {
var i, length, button;
for(i = 0; i < buttons.length; i++) {
button = buttons[i];
[...]
}
}
这里要注意的是变量只存在一次。这些变量在
for
循环的每次迭代时都会更新。当for
循环结束时,所有变量都保持最后一次迭代的“最后”值。
let
... function () {
for(let i = 0; i < buttons.length; i++) {
let button = buttons[i];
[...]
}
}
for
循环周围的花括号。从概念上讲,您可以将其视为forEach
语句:function () {
buttons.forEach(function(item, i) {
var button = buttons[i]; // same as "item"
var length = buttons.length;
[...]
});
}
需要注意的是,
for
循环的每次迭代都会获得自己的变量i
,length
和button
- 不会发生共享。这是区别。