在全局作用域上使用 let 或 const ,虽然在全局作用域上会创建新的绑定,但不会有任何属性被添加到全局对象上。这也就意味着你不能使用 let 或 const 来覆盖一个全局变量,你只能将其屏蔽。
// 在浏览器中,全局作用域下 var 的情况
var RegExp = "Hello!";
console.log(window.RegExp); // "Hello!"
var ncz = "Hi!";
console.log(window.ncz); // "Hi!"
// 在浏览器中,全局作用域下 let 与 const 的情况
let RegExp = "Hello!";
console.log(RegExp); // "Hello!"
console.log(window.RegExp === RegExp); // false
const ncz = "Hi!";
console.log(ncz); // "Hi!"
console.log("ncz" in window); // false
区别:
let 声明的是变量,可以不初始化;const 声明的是常量,必须初始化,且之后不能重新对该常量赋值(若该常量为对象,对象本身不行,但对象成员的值是可以修改的)。
变量提升的实质:当 JS 引擎检视接下来的代码块并发现变量声明时,它会在面对 var 的情况下将声明提升到函数或全局作用域的顶部,而面对 let 或 const 时会将声明放在暂时性死区内。任何在暂时性死区内访问变量的企图都会导致“运行时”错误(runtime error)。只有执行到变量的声明语句时,该变量才会从暂时性死区内被移除并可以安全使用。
循环语句中 var 与 let, const 的比较
for 循环
var 声明
for (var i = 0; i < 10; i++) {
process(items[i]);
}
// i 在此处仍然可被访问
console.log(i); // 10
let 声明
for (let i = 0; i < 10; i++) {
process(items[i]);
}
// i 在此处不可访问,抛出错误
console.log(i);
长期以来, var 的特点使得循环变量在循环作用域之外仍然可被访问,于是在循环内创建函数就变得很有问题。考虑如下代码:
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push(function() { console.log(i); });
}
funcs.forEach(function(func) {
func();
}); // 输出数值 "10" 十次
这是因为变量 i 在循环的每次迭代中都被共享了,意味着循环内创建的那些函数都拥有对于同一变量的引用。在循环结束后,变量 i 的值会是 10 ,因此当 console.log(i) 被调用时,每次都打印出 10 。
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
}
}(i)));
}
funcs.forEach(function(func) {
func();
}); // 从 0 到 9 依次输出
这种写法在循环内使用了 IIFE 。变量 i 被传递给 IIFE ,从而创建了 value 变量作为自身
副本并将值存储于其中。 value 变量的值被迭代中的函数所使用,因此在循环从 0 到 9 的过程中调用每个函数都返回了预期的值。幸运的是,使用 let 与 const 的块级绑定可以在ES6 中为你简化这个循环。
let 声明通过有效模仿上例中 IIFE 的作用而简化了循环。即 let 在每次迭代中,都会创建一个新的同名变量并对其进行初始化。这意味着你可以完全省略 IIFE 而获得预期的结果,就像这样:
var funcs = [];
for (let i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i);
});
}
funcs.forEach(function(func) {
func();
}) // 从 0 到 9 依次输出
与使用 var 声明以及 IIFE 相比,这里代码能达到相同效果,但无疑更加简洁。在循环中, let 声明每次都创建了一个新的 i 变量,因此在循环内部创建的函数获得了各自的 i 副本,而每个 i 副本的值都在每次循环迭代声明变量的时候被确定了。这种方式在 for-in 和 for-of 循环中同样适用,如下所示:
ES6 之前,我们用
var
声明变量,用function
来声明函数。这两种声明方式都有变量提升
的效果。块级声明
ES6 开始,引入了块级作用域,让变量的生命周期更加可控。
块级作用域
(又被称为词法作用域)在如下情况被创建:块级声明,即声明的变量只在在指定的块级作用域内有效,在指定的块级作用域外是无法被访问的。ES6 提供了两种声明方式,来实现块级声明:
let 声明
- 声明变量、const 声明
- 声明常量 & 一般对象。let 声明
与const 声明
变量提升的实质:当 JS 引擎检视接下来的代码块并发现变量声明时,它会在面对 var 的情况下将声明提升到函数或全局作用域的顶部,而面对 let 或 const 时会将声明放在
暂时性死区
内。任何在暂时性死区内访问变量的企图都会导致“运行时”错误(runtime error)。只有执行到变量的声明语句时,该变量才会从暂时性死区内被移除并可以安全使用。循环语句中 var 与 let, const 的比较
for 循环
var 声明
let 声明
长期以来, var 的特点使得循环变量在循环作用域之外仍然可被访问,于是在循环内创建函数就变得很有问题。考虑如下代码:
为了修正这个问题,开发者在循环内使用立即调用函数表达式(IIFEs),以便在每次迭代中强制创建变量的一个新副本,示例如下:
这种写法在循环内使用了 IIFE 。变量 i 被传递给 IIFE ,从而创建了 value 变量作为自身 副本并将值存储于其中。 value 变量的值被迭代中的函数所使用,因此在循环从 0 到 9 的过程中调用每个函数都返回了预期的值。幸运的是,使用 let 与 const 的块级绑定可以在ES6 中为你简化这个循环。
let 声明
通过有效模仿上例中 IIFE 的作用而简化了循环。即 let 在每次迭代中,都会创建一个新的同名变量并对其进行初始化。这意味着你可以完全省略 IIFE 而获得预期的结果,就像这样:与使用 var 声明以及 IIFE 相比,这里代码能达到相同效果,但无疑更加简洁。在循环中,
let 声明
每次都创建了一个新的 i 变量,因此在循环内部创建的函数获得了各自的 i 副本,而每个 i 副本的值都在每次循环迭代声明变量的时候被确定了。这种方式在for-in
和for-of
循环中同样适用,如下所示:本例中的
for-in
循环体现出了与for
循环相同的行为。每次循环,一个新的 key 变量绑定就被创建,因此每个函数都能够拥有它自身的 key 变量副本,结果每个函数都输出了一个不同的值。而如果使用 var 来声明 key ,则所有函数都只会输出 "c" 。在
for-in
和for-of
循环中,const 与 let 的效果一样。唯一的区别是 key 的值在 循环内不能被更改。 const 能够在 for-in 与 for-of 循环内工作,是因为循环为每次迭 代创建了一个新的变量绑定,而不是试图去修改已绑定的变量的值(就像使用了 for 而不是for-in 的上个例子那样)。块级绑定的最佳实践
在默认情况下使用 const ,且只在知道变量值需要被更改的情况下才使用 let 。其理论依据是大部分变量在初始化之后都不应当被修改,因为预期外的改动是 bug 的源头之一。这种理念有着足够强大的吸引力,在你采用 ES6 之后是值得在代码中照此进行探索实践的。