要说清楚 var、let、const 三者的区别,就要从变量提升 Hoisting 和 JavaScript 引擎访问变量时的生命周期说起,且听我慢慢道来...
Hoisting(变量提升)
变量和函数的声明会在编译阶段放入到内存中,给人的感觉就是其声明会移动到整个代码的最顶部。
变量生命周期
JS 引擎访问变量时其生命周期包括如下三个阶段:
- 创建阶段:在作用域中注册一个变量
- 初始化阶段:分配内存,给作用域中的变量创建绑定,变量会自动初始化为
undefined
- 赋值阶段:给已经初始化的变量赋值
var
var 声明的变量,有两个点与 let 和 const 有区别:
-
是用 var 声明的变量会成为 window 对象的属性。
我们可以在 chrome 浏览器的 console 下验证:
var a = 1;
window.a; // 1
let b = 1;
window.b; // undefined
const c = 1;
window.c; // undefined
- 创建阶段和初始化阶段被提升,所以你能看到如下代码是可以执行但不会报错的并且返回的结果为 undefined:
console.log(a); // undefined
var a = 1;
为什么不是 1 呢?因为只有创建阶段和初始化阶段被提升了,而赋值阶段不被提升,上面代码相当于如下代码:
var a = undefined; // 创建阶段 + 初始化阶段
console.log(a);
a = 1; // 赋值阶段
let
关于 let ,我查阅了许多资料,就是想知道用 let 声明的变量到底会不会变量提升,答案是:会
let 声明的变量将在创建阶段就被提升,让我们用代码来证明:
第一阶段:xxx is not defined
console.log(a);
当我们直接打印一个变量时,会报 a is not defined
,原因就是该变量创建都没有创建,当前作用域中没有注册该变量。
第二阶段:ReferenceError: Cannot access 'a' before initialization
console.log(a);
let a;
该阶段可以证明 let 是存在变量提升的,如果不存在变量提升,我们看到的应该如第一个阶段所示的 a is not defined
,但这里却显示的是ReferenceError: Cannot access 'a' before initialization
,翻译一下就是引用错误,在 a 初始化之前我们不能使用 a ,也就是说 let 声明的变量在创建阶段的时候就被提升了,但是不会像 var 那样也提升初始化阶段,除此之外,用 let 声明会产生暂时性死区,这就使得我们不能再用 let 声明完之后又去声明同名的变量。比如:
let a = 1;
var a = 1;
结果:SyntaxError: Identifier 'a' has already been declared
第三阶段:undefined
let a;
console.log(a);
倘若我们将 let a;
放置在打印语句的前面,那么相当于执行了:
// 创建一个变量:a
// 初始化值为 undefined
let a;
console.log(a);
通过上述几个阶段的讨论,我们不仅能知道 let
声明的变量确实存在变量提升 Hoisting 并且会产生暂时性死区,还顺带知道了与 var 的区别。
const
const 与 let 声明变量的机制非常类似,只不过我们必须在声明的时候就指定它的值,因为 const 声明的是一个常量,以后是不能修改的,所以必须声明时就指定值。
用 const 声明创建一个常量,其作用域可以是全局或本地声明的块。 但是与 var 变量不同,全局常量即 const 声明的变量是不会变为 window 对象的属性,这个在 var 那里已经验证过了。
function
这里再简单介绍下 function 的变量提升,function 在创建、初始化、赋值阶段时都存在变量提升。