创建匿名函数时的变量范围

时间:2017-12-12 12:41:52

标签: javascript dom

创建一组按钮时,我遇到了一个奇怪的行为。首先编写它时,我希望每个按钮在单击时显示自己的编号,但显然情况并非如此。但奇怪的是,我仍然会得到两种不同的行为,具体取决于我如何定义应该显示的字符串。所以我的问题是:

  1. 为什么这两个例子产生不同的结果,为什么它们与我们使用innerHTML分配内容时显示的结果不同?
  2. 为获得我原本想要的结果,我需要改变什么? (每个按钮单击显示自己的号码。)
  3. function test(){
    
      var foo = document.getElementById("foo")
      var bar = document.getElementById("bar")
      
      for(var k=0; k < 2; k++){
      
        // 1st example
        var a = document.createElement("button")
        a.innerHTML = "A button "+k
        var str = "click " + k
        a.onclick = function(){alert(str)}
        foo.appendChild(a)
        
        // 2nd example
        var b = document.createElement("button")
        b.innerHTML = "B button "+k
        b.onclick = function(){alert("click " + k)}
        bar.appendChild(b)
        
      }
    }
    
    test()
    <div id="foo">
    
    </div>
    <div id="bar">
    
    </div>

4 个答案:

答案 0 :(得分:2)

这是一个常见问题。所发生的是,所有按钮只有一个var k(以及一个str),结果反映了这一点。在A案例中,str更新的最后一次是k==1,并且显示了k。在B情况下,当循环停止时,function makeOnClick(k) { var msg = "click " + k; return function() { alert(msg); }; } function test() { var foo = document.getElementById("foo") for (var k = 0; k < 2; k++) { var a = document.createElement("button"); a.innerHTML = "A button " + k; a.onclick = makeOnClick(k); foo.appendChild(a) } } test();变为2,因此显示。

解决这个问题的一种方法是以下方法,因为每次调用函数时都会创建一组新的变量。

<div id="foo">

</div>
function makeButton(k) {
    var button = document.createElement("button");
    button.innerHTML = "A button " + k;
    button.onclick = function() {
        alert("Click " + k);
    };
    return button;
}

function test() {
    var foo = document.getElementById("foo")
  
    for (var k = 0; k < 2; k++)
        foo.appendChild(makeButton(k));
}

test();

或者,您可以将按钮的整个创建拉到单独的函数中,这也可以起作用:

<div id="foo">

</div>
{{1}}

底线 - 你需要每个按钮至少有一个函数调用,所以按钮有自己的一组变量..

答案 1 :(得分:1)

您需要阅读有关JavaScript闭包的内容。基本上,您的k变量是在单击回调之外定义的。因此,当您单击按钮时,无论哪一个,k将始终等于它在for循环中的最后一个值(k = 1)。

获得所需结果的一种方法是将变量封装在函数中,如下所示:

function test(){

  var foo = document.getElementById("foo")
  var bar = document.getElementById("bar")
  
  for(var k=0; k < 2; k++){
  
    // 1st example
    var a = document.createElement("button")
    a.innerHTML = "A button "+k
    a.onclick = (function(num){
      return function () {
        alert("click " + num);
      }
    })(k);
    foo.appendChild(a)
    
    // 2nd example
    var b = document.createElement("button")
    b.innerHTML = "B button "+k
    b.onclick = (function(num){
      return function () {
        alert("click " + num);
      }
    })(k);
    bar.appendChild(b);
    
  }
}

test()
<div id="foo">

</div>
<div id="bar">

</div>

答案 2 :(得分:1)

Javascript有一项名为 hoisting 的功能。无论您在函数中声明var,声明都是提升到函数的开头。

此外,vars不是块范围的。因此,循环中的变量将被拉到循环函数外部的开头,并且您只有一个版本,而不是每次迭代中的多个版本。

然后这个单个变量被附加到按钮的clicklistener的函数内的闭包引用。由于这个clicklistener在循环结束后被称为,因此var的值将是最后一次迭代的最后一次。

如果您有机会做出一些架构决策,您可以考虑使用Es 2015,其中引入了新的关键字letlet是块范围的,并且还可以防止多次声明同一个变量。所以这种行为更像是人们所期望的,特别是当你习惯了其他语言时。事实上,最好不要在Es 2015中使用var

答案 3 :(得分:1)

使用let代替var

的另一种解决方案

更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

&#13;
&#13;
function test(){

  var foo = document.getElementById("foo")
  var bar = document.getElementById("bar")
  
  for(let k=0; k < 2; k++){
  
    // 1st example
    let a = document.createElement("button")
    a.innerHTML = "A button "+k
    let str = "click " + k
    a.onclick = function(){alert(str)}
    foo.appendChild(a)
    
    // 2nd example
    let b = document.createElement("button")
    b.innerHTML = "B button "+k
    b.onclick = function(){alert("click " + k)}
    bar.appendChild(b)
    
  }
}

test()
&#13;
<div id="foo">

</div>
<div id="bar">

</div>
&#13;
&#13;
&#13;