阅读 9

ES6 块级绑定

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 。这在代码中能确保基本层次的不可变性,有助于防止某些类型的错误。