从我使用JavaScript开始到现在基本都是在使用var方式声明变量,虽然很早就得知let方式声明变量是更优解,但是在实际练习中并没有遇到过非let不可的情况。不过最近遇到一个for循环的题目彻底颠覆了我的认知。
先上代码:
var a = []; for (var i = 0; i < 3; i++) { a[i] = function () { console.log(i); }; } a[0](); a[1](); a[2]();
我预想三次输出的结果应该是:0、1、2,但是正确结果是:
3 3 3
如果希望上述for循环输出0、1、2的话,将var换为let就可以解决!
更进一步1.0
不过作为新时代青年的我,肯定是想破脑袋都不明白为什么这个for循环里面var和let不同的使用会造成完全不同的两种结果,我决定搬出压寨神器(chrome浏览器F12审查元素)
打开审查元素,给a[0]()设置断点,在a[0]函数执行的时候能捕获以下内容

从上图可得知,该函数体定义为console.log(i),也就是说a[0]在执行的时候才去寻找i的定义,那么这个i是定义在哪里呢?通过审查函数体本身的作用域属性[[Scope]]可以得出如下内容

其实a数组里面的三个函数的作用域都只有一个,就是全局作用域,而且i都是为3,所以在执行的时候其实是去全局作用域中查询i的值,确定i的值为3,然后通过console.log输出。
那么如果把var改为let就会有如下情况

如图所示只是审查了a[0]的函数,如果你分别打开每个函数,都能看到一个block作用域以及不同的i的值。
在for循环中,使用了var来定义i,i是被定义在全局作用域中的,使用let来定义的话,for循环中的函数体涉及到变量i的都会开辟一个block作用域用于存储各自i的值。
更进一步2.0
为什么var定义就是全局作用域,let定义就会另外开辟一个block作用域?如何更好理解这个问题?
其实var定义和let定义在for循环中具体执行应该是存在一定的差异的。因为我们无法得知for循环究竟做了什么,只能通过模拟的方法来反映上面for循环可能的实际执行情况。
在使用var定义i的时候,for循环底层运行应该是这样子的:
var a = []; { var i = 0; if (0 < 3) { a[0] = function () { console.log(i); }; }; i++; if (1 < 3) { a[1] = function () { console.log(i); }; }; i++; if (2 < 3) { a[2] = function () { console.log(i); }; }; i++; } a[0](); a[1](); a[2]();
这样子三次执行函数返回三个3就不难理解了
那么是不是把上面代码中的var改成let再执行一次,就能输出0、1、2呢?
答案仍然是3个3
因为如果for循环中如果用到let定义变量,for循环执行的结构应该是完全不同的,应该是类似于下面的代码:
var a = []; { let i = 0; if (i < 3) { let k = i; a[k] = function () { console.log(k); }; }; i++; if (i < 3) { let k = i; a[k] = function () { console.log(k); }; }; i++; if (i < 3) { let k = i; a[k] = function () { console.log(k); }; }; i++; } a[0](); a[1](); a[2]();
上面这个例子,就能使输出结果为0、1、2,也就是说,使用不同的定义变量的方式会导致for循环的执行方式发生变化。