这是Nicholas Zakas的新作,原文链接:https://github.com/nzakas/understandinges6/blob/master/manuscript/01-Block-Bindings.md
var声明与变量提升现象
这是前ES6时期var的问题,变量会被JS引擎处理成好像它们的声明被放在函数作用域(或者全局作用域)的顶端。
function getValue(condition) {
if (condition) {
var value = "blue";
// other code
return value;
} else {
// value exists here with a value of undefined
return null;
}
// value exists here with a value of undefined
}
经过JS引擎解释后,上面的代码等同于下面的写法:
function getValue(condition) {
var value;
if (condition) {
value = "blue";
// other code
return value;
} else {
return null;
}
}
这个现象以前讨论过很多次,我就不完全复述作者原文了。
区块级别的声明
let的声明
区块作用域的存在要符合两个要求:
- 在函数里;
- 在一对{}里。
用let关键字有以下几个作用:
- 只存在于定义区块内;
- 不会再被提升,见代码:
function getValue(condition) { if (condition) { let value = "blue"; // other code return value; } else { // value doesn't exist here return null; } // value doesn't exist here }
- 不能重复使用同一个变量名;
- 但在不同区块里可以重复使用相同名声明变量,比如子区块,如:
var count = 30; // Does not throw an error if (condition) { let count = 40; // more code }
常量的声明
- 它和let一样,也是区块作用域级别,并且不会被提升;
- 必须在声明时赋值;
- 不可以被修改,对象是特例,下面会说到;
- 也不能够重复使用相同的变量名,不论该名字是通过var还是let声明的。
常量对象
这个地方有点晦涩,作者解释的方法是,const关键字约束的是一个绑定本身,而不是被绑定的值,见代码:
const person = {
name: "Nicholas"
};
// works
person.name = "Greg";
// throws an error
person = {
name: "Greg"
};
这个现象很接近C++里的常量指针,指针指向的地址不能够改变,它指向一个对象后将永远指向它,但是这个对象本身的内容可以被修改。
临时无人区(TDZ)
TDZ这个概念只是在抽象层面存在的一个术语,用于方便解释一些JS引擎的行为,而且它不是ES规范里的内容,在JS引擎遇到var声明时,它的行为和ES3一样,提升变量,在遇到let或者const声明时,则是把变量先放到一个临时无人区,这样没有人能访问它,甚至是typeof,顺便提一下,在ES3里用typeof操作一个未定义变量是安全的,它会返回undefined。直到程序执行到该变量的声明,它的绑定才会被移出无人区,从此能够被访问到。
if (condition) {
console.log(typeof value); // ReferenceError!
let value = "blue";
}
但TDZ有一个奇特的效果:
console.log(typeof value); // "undefined"
if (condition) {
let value = "blue";
}
按说,在if区块之前不应该能够访问到value的。
循环里的区块绑定
ES3里,for循环有一个缺陷,如:
for (var i = 0; i < 10; i++) {
process(items[i]);
}
// i is still accessible here
console.log(i); // 10
这也是个讲过很多次的经典问题了,就不多说了。ES6里,这个问题可以用let解决
for (let i = 0; i < 10; i++) {
process(items[i]);
}
// i is not accessible here - throws an error
console.log(i);
循环里的函数
ES3里的问题如下示例代码:
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push(function() { console.log(i); });
}
funcs.forEach(function(func) {
func(); // outputs the number "10" ten times
});
这也是个经典的老问题了,我就不复述了。以前的解决办法是用闭包:
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
}
}(i)));
}
funcs.forEach(function(func) {
func(); // outputs 0, then 1, then 2, up to 9
});
但是在ES6里,用let就行了。
var funcs = [];
for (let i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i);
});
}
funcs.forEach(function(func) {
func(); // outputs 0, then 1, then 2, up to 9
})
这个行为也适用于for...in循环和for...of循环。
var funcs = [],
object = {
a: true,
b: true,
c: true
};
for (let key in object) {
funcs.push(function() {
console.log(key);
});
}
funcs.forEach(function(func) {
func(); // outputs "a", then "b", then "c"
});
在普通的for循环里,你不能用const声明循环控制变量i,但是在for...in和for...of循环里,可以用const,效果和let一样:
var funcs = [],
object = {
a: true,
b: true,
c: true
};
// doesn't cause an error
for (const key in object) {
funcs.push(function() {
console.log(key);
});
}
funcs.forEach(function(func) {
func(); // outputs "a", then "b", then "c"
});
全局作用域的绑定
不同于var,let和const在全局作用域里声明变量时,它们不再会在window对象上创建新属性,所以它们不会重写window上面的内建对象,比如Date和Math。
作者的建议
使用const,其次是let,抛弃var。