首先我们来看这样一段代码:
for (var i=0; i<3; i++) { setTimeout(() => { console.log(i) }, 0) }很多刷过面试题的jser看到这段代码应该很熟悉了,脱口就能说出正确答案是 3 个 3,但有不少人却不明白其中的原理。别小看这段代码,其中暗含了不少的js的基础知识。 从代码层面来,很多童鞋会想当然的将上述代码理解成以下的内容:
for (var i=0; i<3; i++) { console.log(i) }即,输出为0、1、2。
如果这样想的话,他们至少犯了2个错误:
写出第一段代码的童鞋,都是试图假设循环中的每个迭代在运行时都会给自己“捕获”一个 i 的副本。但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i 。setTimeout等延迟函数的回调会在循环结束时才会执行,故而不管循环多少次,延迟多少时间,console.log中的i,必然是共享的全局作用域中变量i,在此例中就是3。从第一段代码的执行顺序来看,其演示代码应该是:
var i=0; i++; i++; i++; console.log(i); console.log(i); console.log(i);上述代码就很好地解释了第一段代码3个3的执行结果了。
提供两种方法:
使用let for (let i=0; i<3; i++) { setTimeout(() => { console.log(i) }, 0) }let 本质上这是将一段代码块转换成一个可以被关闭的作用域。或许这样看会比较直观。
for (var i=0; i<5; i++) { let j = i setTimeout(() => { console.log(j) }, 0) }let的存在,会产生一个隐形的作用域,使得每次循环的 i 值通过复制给隐形作用域内的 j 保存下来。
使用function创建独立作用域 for (var i=0; i<3; i++) { (function(j) { setTimeout(() => { console.log(j) }, 0) })(i) }众所周知,js 中 function 可以提供一段独立的作用域,那我们就可以通过在迭代内使用function会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。