【问题标题】:javascript hoisting var vs let [duplicate]javascript提升var vs let [重复]
【发布时间】:2016-04-05 22:03:57
【问题描述】:

我正在学习一些ES6 功能,当然还遇到了let 关键字及其新范围(不同于var) 我遇到了一个关于var 的棘手范围及其提升的例子。 但我无法完全理解为什么会得到这个结果:

var events = ['click', 'dblclick', 'keydown', 'keyup'];

for (var i = 0; i < events.length; i++) {
  var event = events[i];
  document.getElementById('btn').addEventListener(event, function() {
    document.getElementById('result').innerHTML = 'event: ' + event;
  });
}
<button id="btn">Click Me!</button>
<span id="result"></span>

我知道var event 被提升到for 循环之外,但为什么每次循环迭代都会获取数组中的最后一个事件('keyup')?
addEventListener 函数是异步的,并且当它附加事件时,事件的值会发生变化?

【问题讨论】:

  • 这不是关于提升,而是关于变量范围和闭包。
  • 我了解元素 ('btn') 在 for 循环之外被提升。 元素没有被提升;变量是。
  • @torazaburo 谢谢,我的错。修好了。
  • 只需将var event = events[i]; 行替换为let event = events[i];。同时,您可以不接受建议使用匿名函数的笨拙的答案,对于块范围变量不再需要。
  • @torazaburo 我想你根本不明白我的问题。我知道let 和它的块作用域和关于var 和它的函数作用域。我的问题是我不明白同一个变量只有 1 个实例,而不是 4 个不同的实例。 @void 帮助我理解了这一点,因此他的回答是有效的。我知道除了 IIFE(如.bind)之外,您还可以通过其他方式解决这个问题,但这与我的问题无关。

标签: javascript ecmascript-6 ecmascript-5 hoisting


【解决方案1】:

在您的示例中,所有事件都被注册,但内部 event 变量和外部 event 变量是不同的,因为没有块级范围,而是函数级范围。

这 4 个事件 'click', 'dblclick', 'keydown', 'keyup' 都已注册,但作为最后的事件值,因为keyup 所以'event: ' + event; 将始终为event: keyup

您可以使用 IIFE (immediately-invoked function expression),它是一种 JavaScript 设计模式,它使用 JavaScript 的函数作用域生成词法作用域。

var events = ['click', 'dblclick', 'keydown', 'keyup'];

for (var i = 0; i < events.length; i++) {
  var event = events[i];
  (function(event) {
    document.getElementById('btn').addEventListener(event, function() {
      document.getElementById('result').innerHTML = 'event: ' + event;
    });
  })(event);
}
<button id="btn">Click Me!</button>
<span id="result"></span>

试图解释:

尝试单击它,evkeyup,然后查看4 seconds 之后的行为。

var events = ['click', 'dblclick', 'keydown', 'keyup'];

for (var i = 0; i < events.length; i++) {
  var event = events[i];
  var ev = events[i];
  document.getElementById('btn').addEventListener(event, function() {
    document.getElementById('result').innerHTML = 'event: ' + ev;
  });
  
  setTimeout(function(){
    ev = "Look it is changed now, see the scoping!";
  },4000);
}
<button id="btn">Click Me!</button>
<span id="result"></span>

【讨论】:

  • 我知道我可以用 IIFE 绕过这种行为,但我仍然不明白为什么它是最后一个值,而不是数组的第一个值('click')
  • @Sag1v 查看更新后的答案。如果解决了,请将其标记为正确答案。
  • 我想我现在明白了。所以基本上它会在内存中创建 1 个点而不是 4 个不同的点?
  • 是的,你可以这么说。
  • 非常感谢您帮我解决这个问题 :)
【解决方案2】:

确实发生了一些“异步”(某种)事件,因为两个 event 事件没有同时评估。

document.getElementById('btn').addEventListener(event, function() {
    document.getElementById('result').innerHTML = 'event: ' + event;
  });

第一个event 立即在循环中求值,因此获取4 个值。

第二个eventid 在包含它的匿名函数被执行 时进行评估,即在事件触发时。在那一刻,JS 引擎返回到event var,它以某种方式保留了对它的引用(这是一个闭包),并找到分配给它的最后一个值。

【讨论】:

  • 这与异步无关。
  • 这就是我在“异步”周围加上引号的原因。要点,以及(可能)令 OP 感到惊讶的是,函数内部事件的评估不是立即的,而是推迟到函数的执行。碰巧在这种特殊情况下,执行也是真正异步的(由事件触发)。
猜你喜欢
  • 2018-06-08
  • 2016-07-01
  • 2014-11-16
  • 1970-01-01
  • 2020-09-29
  • 2021-01-18
  • 1970-01-01
  • 2017-08-18
  • 1970-01-01
相关资源
最近更新 更多