写在前面

以下来源于整理总结参考文档 ES6入门mdn 变量提升

1. 出生日期不同

let 和 var 都是JS中声明变量的方式,但二者的出生日期不同,var是随JS自诞生就有的声明变量的方式,而let 是ES6(ES2015)开始添加的声明变量的方式,目的就是用来弥补 var 声明变量的缺点。

在ES6标准之前的JS只有两种声明变量的方式,即 var 和 function 命令,从ES6开始就又添加了四种声明变量的方式,即 let 、const 、import 、class。

2. 是否变量提升

变量提升这个词也是在ES6出现后才出现的一个名词,在ES6之前根本没有变量提升这个概念,也是为了区分不同的声明变量的方式才产生的概念。

首先在讨论变量提升前必须要理解一个概念,那就是变量的声明和初始化是两个不同的操作。初始化或赋值操作是一个语句,是必须严格按照代码执行顺序执行的,所以变量提升仅仅指的是变量声明这一操作执行时期有所不同,和初始化语句并没有关系。

  1. var a //变量的声明
  2. a = 1 //变量的初始化,也称为赋值
  3. var a = 1 //变量声明 + 初始化

所谓变量提升就是指一个变量被声明的位置和其实际执行顺序是不同的,变量提升会在变量被声明的时候就放到了内存中,待内存所有东西都准备好了(也就是编译好了),才开始从上往下执行代码语句,而此时变量提升使得变量已经存在于内存中了,表面看起来就是 var a 或function f 这种变量声明语句先于代码执行前就被执行了,这就是变量提升。

2.1 var 会变量提升

var 和 function 这两种最早的JS声明变量的方式,都是会变量提升的。所以下面这种形式的语句是可以顺利执行的:

  1. test = 3;
  2. console.log(test);//正确输出3
  3. var test;
  4. console.log(a); //正确输出undefined,因为a确实没赋值但此刻已经存在了
  5. var a;
  1. b();//正确输出hi
  2. function b(){
  3. console.log('hi');
  4. }

由以上代码可以看出,变量提升的声明变量的方式,让所谓的变量需要先声明再使用的共识概念变得模糊,让代码的执行顺序变得看上去不符合逻辑。

2.2 let 不会变量提升

在ES6新标准中,就弥补了 var 声明变量方式的不足,因为新标准升级也得兼容老版本语法,所以不是改变var 声明变量方式的定义和概念,而是新增了一个关键字 let 这种新的声明变量的方式。

let 声明的变量不会变量提升,变量在哪里声明的,就等待代码执行到该处时才执行声明语句,将变量放入内存,那么在此之前 let 声明的变量放在了哪里? 实际是放在了一个叫做 暂存死区 的区域,详细可见 暂存死区, 因此如下语法就是不对的:

  1. console.log(test);//直接报错,test is not defined
  2. let test = 3;

因此 let 声明的变量严格遵循先声明后使用的规则,不存在变量提升的操作。

3. 作用域不同

作用域是指一个变量所能作用的范围,简单说就是一个变量可以存活的范围,变量在其作用域内可存活,出了作用域其也就没了。

有的人会将 var 和 let 理解为 var 是全局变量、let 是局部变量,但其实严格来说这样理解是不对的。全局变量和局部变量根本不是区分 var 和 let 声明方式的方法。

3.1 var 作用域

var 的作用域:全局作用域 和 函数作用域

由于在ES6之前只有全局作用域和函数作用域,因此 var 声明变量方式的作用域就是全局作用域和函数作用域,全局作用域就是var a 声明语句没有被任何函数所包含的,直接声明再最外部的,js代码一执行就执行的。而对应的函数作用域就是指变量声明语句包含在函数内部的,是随函数的执行才执行的。

因此根据 var 作用域不同,在不同作用域声明的同名 var 变量是两个完全独立的变量,只能在各自的领域兴风作浪,出了自己的作用域自己就不存在了,并且各自遵循在各自作用域的变量提升。

  1. function f(){
  2. var test = 'in';
  3. console.log(test);
  4. }
  5. f();//输出 in
  6. onsole.log(test);//报错,test is not defined
  1. var test = 'out';
  2. function f(){
  3. var test = 'in';
  4. console.log(test);
  5. }
  6. console.log(test);//输出 out
  7. f();//输出 in

由以上代码可知,var 声明的变量的变量提升也不是说在一开始编译的时候就把所有 var 声明的变量全部存入内存的,而是先查找全局的 var 声明的变量执行提升存储,处在函数作用域内的 var 声明的变量是在该函数执行的时候将该函数内的直系 var 声明的变量做变量提升,提升到该函数作用域的最前面执行,再提升也超不过其所在函数作用域,如下所示:

  1. var tmp = new Date();
  2. function f() {
  3. console.log(tmp);
  4. if (false) {
  5. var tmp = 'hello world';//不会执行初始化语句,但var tmp变量声明会执行
  6. }
  7. }
  8. f(); // undefined,因为tmp的var声明了只是没赋值,所以不报错,值为undefined
  9. console.log(tmp);//输出当前时间,外部变量tmp值

3.2 let 作用域

let 的作用域:全局作用域 和 块级作用域

let 变量声明方式的出现,也随之出现了新的专属于 let 的作用域,即块级作用域,也就是说在ES6标准发明了 let 和 块级作用域 ,并且规定 let 的作用域是块级作用域。

那么什么是块级作用域呢?那就是用花括号 { } 括起来的一片代码区就是一个块级作用域。

  1. if(true){
  2. let a = 5;
  3. }
  4. console.log(a);//报错 a is not defined

对比 var

  1. if(true){
  2. var a = 5;
  3. }
  4. console.log(a);//正确输出 5

由以上两个代码对比可清晰看到 var 和 let 作用域的区别,实际上来看其实是 let 的作用域相比于 var 更小,更精确了,还不存在变量提升,更规范了。

4. 在同一作用域内是否能重复声明

无论是 var 还是 let ,在不同的作用域中声明相同名的变量是被允许的,因为处在不同作用的同名变量实际是两个相互独立的变量,不会互相影响。因此下列讨论的是在同一作用域中的重复声明情况。

4.1 var 能重复

var 能重复声明变量不会报错,会将变量进行覆盖或合并。如下:

  1. var a = 5;
  2. var a = 2;
  3. console.log(a);//输出 2
  1. var a = 2;
  2. var a;
  3. console.log(a);//输出 2

4.2 let 不能重复

let 一旦声明过了就不能再次声明

  1. let a;
  2. let a = 5;//报错, 'a' has already been declared

5. 全局定义是否会挂在 window 上

5.1 var 会挂在 window 上

var 声明的全局变量,会挂载在 window 对象上,成为 window 窗口对象的一个属性,如下所示:

  1. var a = 5;
  2. console.log(window.a); //输出 5

注意:以上说的是全局定义变量时会挂在 window 上,在函数里用 var 定义变量时不会挂在 window 上。

5.2 let 不会挂在 window 上

let 声明的全局变量就不会挂载在 window 对象上,相比于 var 就更严谨符合逻辑些,因为用户在使用 var 定义全局变量时本意可能并不是要其挂在 window 上,这样会使得 window 上莫名其妙多了好多变量。

  1. let b = 5;
  2. console.log(window.b); //输出 undefined

6. 总结 var 和 let

总的来说,ES6新增的 let 相比于 var 更严谨了许多,因此 let 基本是用来替代 var 的一种更好用的声明变量的方式。let 更是可以解决闭包的一些问题,详见闭包博客。

7. 关于 const

const 也是 ES6 标准的新的定义变量的方式,和 let 同时出生,用法规则也极为相似,相比于 let 不同的是,const 关键字定义的变量是常量,是一个只读属性,值不能被改变,如下:

  1. const a = 5;
  2. a = 6; //报错,const变量不能被改变

由于其不能被改变的特征,自然就引出了一个特点就是:const 变量在声明时必须被初始化,也就是赋值,因为如果此时不赋值,到后面又不能被改变,就成了一个空的、没什么用的常量了,因此,ES6 规定,const 变量在声明时必须初始化,否则就报错,如下:

  1. const a; //报错,const变量必须初始化