试着抓住让vs var好一点

时间:2016-11-30 23:32:43

标签: javascript ecmascript-6

试图掌握两者之间的差异,我正在尝试不同的例子以确保我理解正确。

我已经在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”。它为什么这样做?

谢谢, 詹姆斯。

5 个答案:

答案 0 :(得分:4)

在相关问题的接受答案的第一段中对此进行了解释:

  

区别在于范围界定。 var的范围限定为最近的功能块   并且将其限定为最近的封闭块(如果是,则两者都是全局的)   在任何块之外),它可以小于功能块。

varfor语句块之外的范围内创建一个变量。 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的本地副本,它保留了循环特定迭代的值 - 因此,您可以为每个按下的按钮获得正确的警报消息。

摘要/ TLDR;

  • var 将变量范围定义到定义它的函数
  • 将变量范围扩展到定义它的

  • 在搜索变量的定义时,闭包中引用的变量向外通过作用域链,在全局范围内停止。

希望这有帮助!

答案 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];
      [...]
  }
}

...由JS运行时转换为类似的东西(由于提升):

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循环的每次迭代都会获得自己的变量ilengthbutton - 不会发生共享。这是区别。