JavaScript中的for循环体中var与let的区别

从我使用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循环的执行方式发生变化。

发表评论

电子邮件地址不会被公开。 必填项已用*标注