2

遇到Mozilla 开发者网络中描述的关闭常见错误

它没有按预期工作。无论您关注哪个领域,都会显示有关您年龄的消息。

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);
    }
  }
}

setupHelp();

它确实提供了解决方案。这是添加另一个闭包。

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(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 = makeHelpCallback(item.help);
  }
}

setupHelp(); 

MDN给出的解释是因为第二种解决方案创建了3个环境。

谁能给我解释一下,阶段“环境”是什么意思,我怎么知道它的范围?

4

2 回答 2

3

当您在函数中使用变量名时,JavaScript 解释器首先检查该变量作为函数中的局部变量。如果不存在这样的局部变量,则它会遍历外部范围以查找该变量。(定义函数时,它可以访问作用域链——作用域的层次结构,从定义它的外部函数的作用域开始,然后继续到包含函数的函数,等等。)

在这里,匿名onfocus处理函数使用变量 identifier itemitem不是函数内的局部变量,所以当处理程序被执行时,JavaScript 必须遍历下一个外部作用域,它itemsetupHelp. 您为每个项目定义了一个处理函数;但是,所有这些处理函数在外部范围内都持有相同的引用。item每当调用这些函数中的任何一个时,JavaScript 都会查找相同的值 for ,即循环完成item时它所持有的任何值。for

在有额外闭包的情况下,您将 的当前值item.help作为参数传递给makeHelpCallback,它返回一个处理函数。由于 JavaScript 按值传递变量,因此这些处理函数中的每一个现在都有一个单独的外部作用域,具有不同的help.

于 2013-01-01T22:01:37.147 回答
2

考虑一个更简单的例子:

for (var i = 0; i < 10; ++i) {
    document.getElementById('elem' + i).addEventListener('click', function() {
        document.getElementById('elem' + i).style.display = 'none';
    });
}

代码看起来很简单,对吧?看起来如果您单击一个 div 它将隐藏。

但事实并非如此。事件处理程序在i. i处理程序外部发生的任何事情都发生在处理程序i内部(为了简化一点)。

是此行为的一个示例。请注意,无论您单击哪个 div,ELEM #10它始终是隐藏的。(它是 10 而不是 9,因为i在循环条件失败之前最后一次递增。)


一个诱人的解决办法是试试这个:

for (var i = 0; i < 10; ++i) {
    document.getElementById('elem' + i).addEventListener('click', function() {
        var fix = i;
        document.getElementById('elem' + fix).style.display = 'none';
    });
}​

但这不起作用,因为直到iis already才处理分配10。(好吧,从技术上讲,直到点击事件发生时才会处理它——假设那时i是 10,尽管理论上点击可能在所有事件被绑定之前发生,i当时低于 10。)


您实际上可以使用 MDN 内联的技巧:

for (var i = 0; i < 10; ++i) {
    document.getElementById('elem' + i).addEventListener('click', (function(j) { return function() {
        document.getElementById('elem' + j).style.display = 'none';
    }}(i)));
}​

将变量传递给函数使其被视为不同的“环境”。函数内部j在函数被调用时被冻结。然后内部闭包使用那个 j。有趣的是,i仍然存在于这两个函数内部。根据访问它的时间,它会介于0和之间10

for (var i = 0; i < 10; ++i) {
    document.getElementById('elem' + i).addEventListener('click', (function(j) { 
        alert(i); //whichever value is being bound
        //note that i === j is always true here
        //j is a copy of i though and thus stops changing when i changes in the future 
        return function() {
            alert(i); //always 10
            document.getElementById('elem' + j).style.display = 'none';
        };
    }(i)));
}​

考虑 JS 作用域的一种简化方法是嵌套函数继承父作用域(这是真的)。当函数被调用时,变量在调用范围内被传递。

这里有一个有趣的小警告,原语是按值传递的,对象是按引用传递的。

var x = 5; (function(n) { ++n; }(x)); /* x is still 5 */
var o = {foo: 'bar'}; (function(obj) { obj.far = 'baz'; }(o)); /* o === {foo: 'bar', far: 'baz'} */ 
于 2013-01-01T22:03:02.013 回答