未分类

ES6:看不懂的let类型与闭包间的关系+Babel的bug

let是ES6变量类型,其只在代码块内有效。
闭包是有函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时所能访问的所有局部变量。

如果合理使用闭包,可以使用var实现let局部有效的效果。显然这是多余的,因为babel已经能帮我们做了,并且很多新版浏览器都支持let,所有本文结束。

等等,babel都干了啥?为啥能将let转码使代码兼容旧版浏览器?

兼容转码

我们先来看一段比较简单的babel转码



我们来搬一段特别复杂的let类型申明代码

code1
1
2
3
4
5
6
7
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6

babel官网将其转码为兼容代码:

code2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
"use strict";

var a = [];

var _loop = function _loop(i) {
a[i] = function () {
console.log(i);
};
};

for (var i = 0; i < 10; i++) {
_loop(i);
}
a[6](); // 6

老司机马上就看出了:将let转为了闭包

闭包

我们来看看MDN上的一段例子

code3
1
2
3
4
5
6
7
8
9
10
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}

var myFunc = makeFunc();
myFunc();

闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。
我们一通分析便可以看出code3闭包的基本结构:

code4
1
2
3
4
5
function (j) {
return function () {
console.log(j)
}
}(i)

code4的闭包是立即执行

利用闭包,使var == let

code4的结构不正是code2的简写吗?我们可以将code2改写为:

code5
1
2
3
4
5
6
7
8
9
10
11
12
"use strict";

var a = [];

for (var i = 0; i < 10; i++) {
a[i] = function (j) {
return function () {
console.log(j)
}
}(i)
}
a[6](); // 6

说好的简写呢?!
仔细阅读这句话闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。

let是块级作用域,也就是局部作用域。闭包也是能访问局部作用域。

code4正是将变量i赋值给函数的局部变量jcode5中数组a的每个item都是该函数实例的引用。
let出现之前,在循环中写闭包是一个比较常见的问题。

我们不鼓励在循环中创建闭包,过多的闭包会付出性能代价。

Babel的bug

意外发现了一个Babel的小bug。我们在最新的Chrome/Firefox浏览器中运行这段代码:

code6
1
2
3
4
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i, new Date().getTime());
}

会发现输出结果为3次”abc 1539160907***”。将code6通过Babel转码得到code7

code7
1
2
3
4
for (var i = 0; i < 3; i++) {
var i = 'abc';
console.log(i, new Date().getTime());
}

code7在浏览器中仅仅运行了1次,输出结果为”abc 1539160907***”

阮老师讲:

for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

分享到