var 声明与变量提升
使用 var 关键字声明的变量,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部(如果声明不在任意函数内,则视为在全局作用域的顶部)。这就是变量提升,不过变量的声明虽然被提升到了顶部,但初始化工作还保留在原处。
function getValue(condition) { if (condition) { var value = "blue"; // 其他代码 return value; } else { // value 在此处可访问,值为 undefined return null; } // value 在此处可访问,值为 undefined }
块级声明
块级声明让所声明的变量在指定块的作用域外无法被访问。
块级作用域在如下情况被创建:
1. 在一个函数内部
2. 在一个代码块(由一对花括号包裹)内部
let声明
let 声明的语法与 var 的语法一致,但会将变量的作用域限制在当前代码块中,并且不会被提升到当前代码块的顶部。
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; // 其他代码 }
常量声明
使用 const 声明的变量会被认为是常量,它们的值在被设置完成后就不能再被改变。
正因为如此,所有的 const 变量都需要在声明时进行初始化。
常量声明与 let 声明一样都是块级声明,在声明它们的语句块外部无法访问,并且声明也不会被提升。
const 声明在同一作用域(全局或是函数作用域)内定义一个已有变量时也会和let一样抛出错误。
如果 const 声明的是对象,阻止的是对绑定值的修改,不会阻止对成员值的修改。
const person = { name: "Nicholas" }; // 正常 person.name = "Greg"; // 抛出错误 person = { name: "Greg" };
暂时性死区
使用 let 或 const 声明的变量,在达到声明处之前都是无法访问的,试图访问会导致一个引用错误,即使在通常是安全的操作时(例如使用 typeof 运算符)。
if (condition) { console.log(typeof value); // 引用错误 let value = "blue"; }
JS 引擎检视接下来的代码块并发现变量声明时,它会在面对 var 的情况下将声明提升到
函数或全局作用域的顶部,而面对 let 或 const 时会将声明放在暂时性死区(temporal dead zone,TDZ )内。任何在暂时性死区内访问变量的企图都会导致运行时错误。
然而,可以在变量被定义的代码块之外对该变量使用 typeof。
console.log(typeof value); // "undefined" if (condition) { let value = "blue"; }
当 typeof 运算符被使用时, value 并没有在暂时性死区内,因为这发生在定义 value 变量的代码块外部。
循环中的块级绑定
下面例子用var 声明导致变量提升,结果i为10。
for (var i = 0; i < 10; i++) { } console.log(i);//10
如果把var改成let,则会抛出错误。
for (var i = 0; i < 10; i++) { } console.log(i);//抛出错误:i is not defined。
因为let定义的变量i仅在for循环内部可用。
循环内的函数
var funcs = []; for (var i = 0; i < 10; i++) { funcs.push(function() { console.log(i); }); } funcs.forEach(function(func) { func(); // 输出数值 "10" 十次 });
因为变量 i 在循环的每次迭代中都被共享了,意味着循环内创建的那些函数都拥有对于同一变量的引用。
修正这个问题,在循环内使用立即调用函数表达式(IIFEs),以便在每次迭代中强制创建变量的一个新副本:
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 依次输出 });
变量 i 被传递给 IIFE,从而创建了 value 变量作为自身副本并将值存储于其中。
使用 let 与 const 的块级绑定可以简化这个循环
var funcs = []; for (let i = 0; i < 10; i++) { funcs.push(function() { console.log(i); }); } funcs.forEach(function(func) { func(); // 从 0 到 9 依次输出 })
在循环中let 声明每次都创建了一个新的 i 变量,因此在循环内部创建的函数获得了各自的 i 副本。
如果上面let改成const,则会在一次迭代后因为试图改变i的值而抛出错误。
let这种方式在 for-in和 for-of 循环中同样适用:
var funcs = [], object = { a: true, b: true, c: true }; for (let key in object) { //let可以改成const funcs.push(function() { console.log(key); }); } funcs.forEach(function(func) { func(); // 依次输出 "a"、 "b"、 "c" });
const不能用于常规for循环,但可以用于for-in 和 for-of,因为循环为每次迭代创建了一个新的变量绑定,而不是试图去修改已绑定的变量的值。
块级绑定新的最佳实践
因为大部分变量在初始化之后都不应当被修改,所以在默认情况下使用 const 、并且只在知道变量值需要被更改的情况下才使用 let 。