块级作用域和let

在学习Javascript的一开始,我们就使用var来创建变量,随着ES6的发布,出现了一个一个新的创建变量的方式let,随着浏览器和node的快速支持以及babel的出现,我们已经可以正式使用let。在很多情况下用于比起使用var,let会更好,不只是在代码的稳定性还可以增加代码的可读性。

我们在学习let的时候,经常会看到对于let的定义

The let statement declares a block scope local variable,optionally initializing it to a value.(let语句声明一个块级作用域的本地变量,并且可选的赋予初始值。) — let MDN

显而易见的,let就是作用于块级作用域,那么我们首先来了解一下什么是块级作用域(block scope)。

对于块级作用域MDN的定义是

语句块 (或其他语言中的 复合语句) 用来组织零个或多条语句. 用一对花括号界定语句块. — block MDN

语法

1
2
3
4
5
6
[label:] {
statement_1;
statement_2;
...
statement_n;
}

MDN对于语法的说明,给了我们一个说明,下面while (x < 10)也是块级作用域的一部分。

1
2
3
while (x < 10) {
x++;
}

所以简单来说let在{}以内或者在类似while (x < 10)()中定义的变量,不会被外层的作用域访问到,如下的例子

1
2
3
4
if (true) {
let x = 2;
}
console.log(x); // Uncaught ReferenceError: x is not defined

同时,我们看下面这个例子

1
2
3
4
5
6
7
8
9
10
'use strict';

let a = 1;

{
let a = 2;
console.log(a) // 2
}

let a = 1;

这段代码也很好理解,我要说的是,我们一开始申明了'use strict';,表示我们使用了严格模式,我们两次定义了a变量,但是代码正常执行了,说明了{}产生了新的块作用域,所以代码正常执行了。

另外对于let,我们可以看情况将其包裹在{}中,为变量显式声明块作用域,这样更加符合let的块级作用域的定义,也可以方便js的垃圾回收机制来判断是否需要回收,也可以增加代码的模块化和可读性,例子如下

1
2
3
4
5
6
7
8
9
10
11
function process(data) {
// 在这里做点有趣的事情
}
// 在这个块中定义的内容可以销毁了!
{
let someReallyBigData = { .. };

process( someReallyBigData );
}

// 做其他的事情

最后,我来解答一道网上经常看到的题目,题目和详细解答在我的另一篇文章中也有,我个人认为最好的答案是,只需要把var改为let,其实这道题目的本质在于var不是块级作用域,而我们的代码是写在块级作用域中的,所以我们需要一个块级作用域的变量声明,保证每个块级作用域都是不一样的变量,相互不影响,而let刚好。

1
2
3
4
5
6
7
8
9
10
11
for ( let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
},i*1000 );
}
// 相隔1s输出
// 1
// 2
// 3
// 4
// 5