var 声明提升 使用 var 时,下面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域 顶部:
function foo() { console.log(age); var age = 26; } foo(); // undefined之所以不会报错,是因为 ECMAScript 运行时把它看成等价于如下代码:
`function foo() { var age; console.log(age); age = 26; } foo(); // undefined这就是所谓的“提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次 使用 var 声明同一个变量也没有问题:
function foo() { var age = 16; var age = 26; var age = 36; console.log(age); } foo(); // 36let 声明 let 跟 var 的作用差不多,但有着非常重要的区别。最明显的区别是,let 声明的范围是块作用域,而 var 声明的范围是函数作用域。
if (true) { var name = 'Matt'; console.log(name); // Matt } console.log(name); // Matt if (true) { let age = 26; console.log(age); // 26 } console.log(age); // ReferenceError: age 没有定义let 也不允许同一个块作用域中出现冗余声明。这样会导致报错:
var name; var name; let age; let age; // SyntaxError;标识符 age 已经声明过了对声明冗余报错不会因混用 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量, 它们只是指出变量在相关作用域如何存在。
var name; let name; // SyntaxError let age; var age; // SyntaxErrorlet 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升。 在解析代码时,JavaScript 引擎也会注意出现在块后面的 let 声明,只不过在此之前不能以任何方式来引用未声明的变量。在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。
// name 会被提升 console.log(name); // undefined var name = 'Matt'; // age 不会被提升 console.log(age); // ReferenceError:age 没有定义 let age = 26;全局声明 与 var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性(var 声明的变量则会)。
var name = 'Matt'; console.log(window.name); // 'Matt' let age = 26; console.log(window.age); // undefined条件声明 对于 let 这个新的 ES6 声明关键字,不能依赖条件声明模式
<script> let name = 'Nicholas'; let age = 36; </script> <script> // 假设脚本不确定页面中是否已经声明了同名变量 // 那它可以假设还没有声明过 if (typeof name === 'undefined') { let name; } // name 被限制在 if {} 块的作用域内 // 因此这个赋值形同全局赋值 name = 'Matt'; try { console.log(age); // 如果 age 没有声明过,则会报错 } catch(error) { let age; } // age 被限制在 catch {}块的作用域内 // 因此这个赋值形同全局赋值 age = 26; </script>for 循环中的 let 声明
let 出现之前,for 循环定义的迭代变量会渗透到循环体外部:
for (var i = 0; i < 5; ++i) { // 循环逻辑 } console.log(i); // 5改成使用 let 之后,这个问题就消失了,因为迭代变量的作用域仅限于 for 循环块内部:
for (let i = 0; i < 5; ++i) { // 循环逻辑 } console.log(i); // ReferenceError: i 没有定义在使用 var 的时候,最常见的问题就是对迭代变量的奇特声明和修改:
for (var i = 0; i < 5; ++i) { setTimeout(() => console.log(i), 0) } // 你可能以为会输出 0、1、2、3、4 // 实际上会输出 5、5、5、5、5之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的 i 都是同一个变量,因而输出的都是同一个最终值。
而在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
for (let i = 0; i < 5; ++i) { setTimeout(() => console.log(i), 0) } // 会输出 0、1、2、3、4因为对于 let 来说,他会创建一个块级作用域,相当于
{ // 形成块级作用域 let i = 0 { let ii = i setTimeout( function timer() { console.log( ii ); }, 0 ); } i++ { let ii = i } i++ { let ii = i } ... }const 的行为与 let 基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。
const age = 26; age = 36; // TypeError: 给常量赋值 // const 也不允许重复声明 const name = 'Matt'; const name = 'Nicholas'; // SyntaxError // const 声明的作用域也是块 const name = 'Matt'; if (true) { const name = 'Nicholas'; } console.log(name); // Mattconst 声明的限制只适用于它指向的变量的引用。换句话说,如果 const 变量引用的是一个对象, 那么修改这个对象内部的属性并不违反 const 的限制。
const person = {}; person.name = 'Matt'; // okJavaScript 引擎会为 for 循环中的 let 声明分别创建独立的变量实例,虽然 const 变量跟 let 变 量很相似,但是不能用 const 来声明迭代变量(因为迭代变量会自增):
for (const i = 0; i < 10; ++i) {} // TypeError:给常量赋值