JavaScript中的let与var有什么区别?

5
我知道let具有块级作用域,而var具有函数级作用域。但是在这种情况下,我不明白如何使用let可以解决问题。
const arr = [1,2,3,4];
for (var i = 0; i < arr.length; i++) {
setTimeout(function() {
   console.log(arr[i]) 
}, 1000);
} // Prints undefined 5 times

const arr = [1,2,3,4];
for (let i = 0; i < arr.length; i++) {
setTimeout(function() {
   console.log(arr[i]) 
}, 1000);
} // Prints all the values correctly
3个回答

4

这都与变量的作用域有关。让我们尝试将两个部分包装到函数中,并观察输出:

function test() {
  // `i` will be declared here, making it a non-for-loop scoped variable
  const arr = [1, 2, 3, 4];
  for (var i = 0; i < arr.length; i++) {
    setTimeout(function() {
      console.log(arr[i])
    }, 1000);
  } // Prints undefined 5 times
}

test();

在第一个情况下,i会被提升,由于setTimeout的异步性质,循环结束后i会立即变为4,这将使arr[i]指向数组中的一个undefined元素。
在第二个情况下,i没有被提升,并且具有对每次循环迭代的作用域访问权限,使得i能够准确地被console.log语句使用。因此结果符合预期:

function test() {
  const arr = [1, 2, 3, 4];
  for (let i = 0; i < arr.length; i++) {
    setTimeout(function() {
      console.log(arr[i])
    }, 1000);
  } // Prints all the values correctly

}

test();


1
我仍然不明白。let变量是在for循环内部作用域还是外部?有四个不同的i吗,还是只有一个?如果只有一个,那么它与var情况有何区别(假定只有一个i 也会被闭包捕捉,就像var一样)?如果有四个,为什么i++有效(即如何访问旧的i和新的i)?如果有ES2018相关段落,那就太棒了。(编辑:提议的重复问题已经有答案了。) - Amadan
抱歉,我不太明白。您能否解释一下“提升”是什么意思? - Souvik Ghosh
In the second case, i is not hoisted, and has scoped access to each iteration of the loop, making i accurately available to console.log statement. That is right but the execution of loop won't stop for setTimeout.In this case also the loop will finish and if you log the value of i outside settimeout, you will see it's value i is set to length-1 much before the settimeout have started execution. So the question is how settimeout get the value of i even when its value is set length-1 - brk
@traktor53 我了解关于块的问题;我的问题主要是i++在哪个范围内,获取澄清它的规范段落的参考将会很棒。如果i++在作用域内,那么它应该在每次迭代中使用相同的变量,因为++修改了一个现有的变量;如果i++在作用域外,则应该未知i;但这两者都不是实际发生的情况。从babel编译的版本来看,我可以看到发生了什么(外部作用域的i传递为值到内部作用域的i),但我不知道如何在规范中解释它。 - Amadan
1
在ES6+中,i在块内部,每次迭代都会创建一个新的词法环境。根据ECMA2017版第13.7.4.7节中的“IterationStatement : for ( LexicalDeclaration Expression ; Expression ) _Statement_”,第2步创建了一个循环环境,并在第7步设置了该环境。根据第13.7.4.8节,循环环境在第一次循环迭代之前(第2步)被复制,并在循环结束之前以其现有状态再次复制,然后进行`++i'增量(第3.e步)。循环终止后恢复先前的环境。 - traktor
显示剩余2条评论

1

首先,输出的次数将是四次而不是五次(如您在评论中提到的)。我将您的代码粘贴到Babel REPL中,这就是我得到的结果,

"use strict";

var arr = [1, 2, 3, 4];

var _loop = function _loop(i) {
setTimeout(function () {
   console.log(arr[i]);
}, 1000);
};

for (var i = 0; i < arr.length; i++) {
_loop(i);
}

你现在看到let是如何在内部工作的了吗?:-)


0

您仍然可以在 setTimeout 中使用 var。 您可以使用立即调用的函数表达式(IIFE)创建一个闭包,使得 setTimeout 函数能够识别 i 的值。

const arr = [1,2,3,4];
for (var i = 0; i < arr.length; i++) {
(function(i){
setTimeout(function() {
   console.log(arr[i]) 
}, 1000)})(i);
}


4
这并没有回答“为什么 let 起作用”的问题。 - Amadan
谢谢你的回答,Ankit。但是我知道如何使用IIFE使其工作。我只是想知道'let'是如何使其工作的。 - Krishna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接