问题
以下两段代码在执行结果上有何不同?
代码1:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[3]();
a[4]();
代码2:
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[3]();
a[4]();
答案是代码1在执行的执行结果均为10,代码2的执行结果为3和4
解析
var的情况
我们知道在没有let的年代,var的作用域仅仅取决于它是否在函数中,在函数中的为局部作用域,在函数外的为全局作用域。
而在代码1中
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[3]();
a[4]();
由于没有块级作用域的存在,因此for循环体中定义的变量也属于全局作用域范围,举个例子:
for (var i = 0; i < 10; i ++);
console.log(i); // ->10
while(1){
var j = 3;
if(j>2){
break;
}
}
console.log(j); // -> 3
因此在for循环内部定义的函数不会形成闭包,在for循环执行结束后,i的值累加到10
arr数组中的元素仅仅包含函数体的内容,在后来的arr[3]
中将会调用console,log(i)
,i此时为全局作用域下的i,值为10,因此arr[3]和arr[4]输出的值都为10
let的情况
let定义的变量在for while if
等大括号包裹的区域中称为块级别的作用域,我们知道闭包其实是在局部作用域函数访问上一级局部作用域变量形成的,有别于ES5,ES6中let可以形成多种的局部作用域,在代码2中
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
首先在此循环中let声明的变量i相当于在ES5中函数范围内的变量(局部变量),而在循环过程中每一次将a[i]变量指向一个新的匿名函数,这个匿名函数内部调用了上级的局部变量i,因此新的匿名函数构成了闭包。
闭包每次会将自己(函数体)和当时的活动变量(i)打包供给arr[i]访问,arr[i]称为此副本函数的一个引用。
在整个循环过程中形成的10个副本含有10个全然不同的i,因此调用arr[3]和arr[4]的函数呈现的值也不同。
可以作如下修改让结果更清晰:
var a = [];
for (let i = 0; i < 10; i++) {
// 作用域a
a[i] = function () {
// 作用域b
console.log(i*i);
};
}
a[3](); // 9
a[4](); // 16
// console.log(i); // 试图输出局部变量i,报错