var,let与const
var:
使用var操作符定义的变量会成为包含它的函数的局部变量。比如,使用 var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:
function test() { var message = "hi"; // 局部变量 } test(); console.log(message); // 出错!
var操作符具有变量提升属性,使用这个定义的变量会自动提升到函数作用域顶部:
function foo() { console.log(age); var age = 26; } foo(); // undefined
ES运行时将其等效为:
function foo() { var age; console.log(age); age = 26; } foo(); // undefined
let
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 没有定义
在这里,age变量之所以不能在if块外部被引用,是因为它的作用域仅限于该块内部。块作用域是函数作用域的子集,因此适用于var的作用域限制同样也适用于let。
同时,let的声明变量不会在作用域中进行提升。在解析代码时,JavaScript引擎也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为“暂时性死区(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError。
const:
const与let基本相同,唯一一个重要区别是用它声明的变量同时必须初始化,并且不能进行修改,const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制。
for循环中的声明:
var在for循环中会渗透到循环体的外部,改成使用let之后,这个问题就消失了,因为迭代变量的作用域仅限于 for循环块内部:
for (var i = 0; i < 5; ++i) { // 循环逻辑 } console.log(i); // 5 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声明时,js引擎会为每一次循环声明一个新的迭代变量,每个setTimeout引用的都是不同的变量实例:
for (let i = 0; i < 5; ++i) { setTimeout(() => console.log(i), 0) } // 会输出 0、1、2、3、4
虽然const变量跟let变量很相似,但是不能用const来声明迭代变量(因为迭代变量会自增):
for (const i = 0; i < 10; ++i) {} // TypeError:给常量赋值