# ❓ var、let 和 const 区别的实现原理是什么

# 区别

# var

var 声明的变量,不存在块级作用域,在全局范围内都有效(顶层变量)

变量提升

内层变量可能覆盖外层变量

用来计数的循环变量泄露为全局变量

# let

  • let 声明块级作用域变量

    for (let i = 0; i < length; i++) {}
    
    1
  • 不存在变量提升

// var 的情况
console.log(foo) // 输出undefined
var foo = 2

// let 的情况
console.log(bar) // 报错ReferenceError
let bar = 2
1
2
3
4
5
6
7
  • 暂时性死区
var tmp = 123

if (true) {
  tmp = 'abc' // ReferenceError
  let tmp
}
1
2
3
4
5
6

上面代码中,存在全局变量 tmp,但是块级作用域内 let 又声明了一个局部变量 tmp,导致后者绑定这个块级作用域,所以在 let 声明变量前,对 tmp 赋值会报错。

ES6 明确规定,如果区块中存在 letconst 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

  • 不允许重复声明

let 不允许在相同作用域内,重复声明同一个变量。

# const

const 声明一个只读的常量。一旦声明,常量的值就不能改变。

const 声明的变量不得改变值,这意味着,const 一旦声明变量,就必须立即初始化,不能留到以后赋值。

const 声明的常量,也与 let 一样不可重复声明。

const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

# 本质

const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {}

// 为 foo 添加一个属性,可以成功
foo.prop = 123
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {} // TypeError: "foo" is read-only
1
2
3
4
5
6
7
8

上面代码中,常量 foo 储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把 foo 指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属性。