变量提升与暂时性死区

703 阅读3分钟

从变量提升说起

在 ES5的“var” 时代,有一个特别的现象:不管我们的变量声明是写在程序的哪个角落,最后都会被提到作用域的顶端去。


console.log(num)   //undefined

var num = 1

神奇的变量提升

这段代码不会报错,反而会输出一个 undefined。这就是因为变量的声明被“提升”了,它等价于这样:


var num
console.log(num)  // undefined
num = 1

上面这个例子里,我们看到 num 作为全局变量会被提升到全局作用域的头部。在函数作用域里,也会有类似的现象发生:

function getNum() {
  console.log(num)  // undefined
  var num = 1  
}

这里同样会输出 undefined,这是因为函数内部的变量声明会被提升至函数作用域的顶端。上面这个例子其实等价于:

function getNum() {
  var num 
  console.log(num)  // undefined
  num = 1  
}

变量提升的原理

事实上,JS也是有编译阶段的,它和传统语言的区别在于,JS不会早早地把编译工作做完,而是一边编译一边执行。简单来说,所有的JS代码片段在执行之前都会被编译,只是这个编译的过程非常短暂(可能就只有几微妙、或者更短的时间),紧接着这段代码就会被执行。

没错,JS 和其他语言一样,都要经历编译和执行阶段。正是在这个短暂的编译阶段里,JS 引擎会搜集所有的变量声明,并且提前让声明生效。至于剩下的语句,则需要等到执行阶段、等到执行到具体的某一句的时候才会生效。这就是变量提升背后的机制。

被禁用的变量提升

console.log(num)  let num = 1 
VM324:1 Uncaught ReferenceError:  num is not defined     at <anonymous>:1:13

理解 let 的时候可以参考 var。let 和 var 非常相似,let 区别于 var 的最关键的地方在于:当我们用 let 声明变量时,变量会被绑定到块作用域上,而 var 是不感知块作用域的。

值得注意的是,这个规则在声明引用类型时有点不同——引用类型的属性值
(包括数组的元素)可以被更改,只要你不修改引用的指向。

const me = {
  name: 'Glennley'
}

me.name = 'zhangsan' // 没问题
const arr = [1,2,3,4]
arr.push(2)
 // arr [1, 2, 3, 4, 2]

arr.splice(0,1) 
// [2, 3, 4, 2]

const arrObj = {a:1,b:2}
arrObj.a = 2
arrObj.d = 'c:3'

// {a: 2, b: 2, d: "c:3"}

暂时性死区

这样的代码也经常作为面试题来出。面试官会问你:这段代码的运行结果是啥? 事实上,这段代码啥也运行不出来,它会报错:

这是因为 ES6 中有明确的规定:如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。假如我们尝试在声明前去使用这类变量,就会报错。
这一段会报错的危险区域,有一个专属的名字,叫”暂时性死区“。在下面 demo 中,me = ‘glennley’ 区域就是暂时性死区。

{
    me = 'glennley'   //即暂时性死区
    let me;
}