var 声明与变量提升
使用 var 关键字声明的变量,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部,如果声明不在任意函数内,则视为在全局作用域的顶部。
function getValue(condition) {
if (condition) {
var value = "blue";
// 其他代码
return value;
} else {
// value 在此处可访问,值为 undefined return null;
}
// value 在此处可访问,值为 undefined
}
如果你不太熟悉 JS ,或许会认为仅当 condition 的值为 true 时,变量 value 才会被创建。但实际上,value 无论如何都会被创建。 JS 引擎在后台对 getValue 函数进行了调整, 就像这样:
function getValue(condition) {
var value;
if (condition) {
value = "blue";
// 其他代码
return value;
} else {
return null;
}
}
value 变量的声明被提升到了顶部,而初始化工作则保留在原处。这意味着在 else 分支内 value 变量也是可访问的,此处它的值会是 undefined ,因为它并没有被初始化。
块级声明
块级声明也就是让所声明的变量在指定块的作用域外无法被访问(在一个函数内部 或 在一个代码块内部)。
let 声明
let 声明的语法与 var 的语法一致。你基本上可以用 let 来代替 var 进行变量声明,但会将变量的作用域限制在当前代码块中(其他细微差别会在稍后讨论)。由于 let 声明并不会被提升到当前代码块的顶部,因此你需要手动将 let 声明放置到顶部,以便让变量在整个代码块内部可用。
function getValue(condition) {
if (condition) {
let value = "blue";
// 其他代码
return value;
} else {
// value 在此处不可用
return null;
}
// value 在此处不可用
}
禁止重复声明
如果一个标识符已经在代码块内部被定义,那么在此代码块内使用同一个标识符进行 let 声明就会导致抛出错误。
var count = 30;
// 语法错误
let count = 40;
在嵌套的作用域内使用 let 声明一个同名的新变量,则不会抛出错误。
var count = 30;
// 不会抛出错误
if (condition) {
let count = 40;
// 其他代码
}
常量声明
ES6 中里也可以使用 const 语法进行声明。使用 const 声明的变量会被认为是常量( constant ),意味着它们的值在被设置完成后就不能再被改变。正因为如此,所有的 const 变量都需要在声明时进行初始化。试图对之前用 const 声明的常量进行赋值会抛出错误。
// 有效的常量
const maxItems = 30;
// 语法错误:未进行初始化
const name;
// 抛出错误
const maxItem = 50;
常量声明与 let 声明一样,都是块级声明。这意味着常量在声明它们的语句块外部是无法访问的,声明不会被提升,并且在同一个作用域内不能重复定义。
使用 const 声明对象比较特殊,const 声明会阻止对于变量绑定与变量自身值的修改,这意味着 const 声明并不会阻止对变量成员的修改。
const person = {
name: "Nicholas"
};
// 工作正常
person.name = "Greg";
// 抛出错误
person = {
name: "Greg"
}
暂时性死区
当 JS 引擎检视接下来的代码块并发现变量声明时,它会在面对 var 的情况下将声明提升到函数或全局作用域的顶部,而面对 let 或 const 时会将声明放在暂时性死区内。任何在暂时性死区内访问变量的企图都会导致“运行时”错误(runtime error)。只有执行到变量的声明语句时,该变量才会从暂时性死区内被移除并可以安全使用。
循环中的块级绑定
for (var i = 0; i < 10; i++) {
process(i);
}
// i 在此处仍然可被访问
for (let i = 0; i < 10; i++) {
process(i);
}
// i 在此处不可访问,抛出错误
console.log(i);
因为 var 声明导致了变量提升
循环内的常量声明
在常规的 for 循环中,你可以在初始化时使用 const ,但循环会在你试图改变该变量的值时抛出错误。因为该语句试图更改常量的值。因此,在循环中你只能使用 const 来声明一个不会被更改的变量。
var funcs = [];
// 在一次迭代后抛出错误
for (const i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i);
});
}
const 变量在 for-in 或 for-of 循环中使用时,与 let 变量效果相同。因此下面代码不会导致出错,因为循环为每次迭代创建了一个新的变量绑定,而不是试图去修改已绑定的变量的值。
var funcs = [], object = {
a: true, b: true, c: true
};
// 不会导致错误
for (const key in object) {
funcs.push(function() {
console.log(key);
});
}
funcs.forEach(function(func) {
func(); // 依次输出 "a"、 "b"、 "c"
});
全局块级绑定
- 当在全局作用域上使用 var 时,它会创建一个新的全局变量,并成为全局对象(在浏览器中是 window )的一个属性。这意味着使用 var 可能会无意覆盖一个已有的全局属性。
- 若你在全局作用域上使用 let 或 const ,虽然在全局作用域上会创建新的绑定,但不会有任何属性被添加到全局对象上。这也就意味着你不能使用 let 或 const 来覆盖一个全局变量,你只能将其屏蔽。
- 当你不想在全局对象上创建属性时,这种特性会让 let 与 const 在全局作用域中更安全。
- 想让代码能从全局对象中被访问,你仍然需要使用 var 。在浏览器中跨越帧或窗口去访问代码时,这种做法非常普遍。
块级绑定最佳实践
在默认情况下使用 const ,而只在你知道变量值需要被更改的情况下才使用 let 。这在代码中能确保基本层次的不可变性,有助于防止某些类型的错误。