1. 变量提升-函数声明提升的本质

在js代码中,通过var定义的变量或者是通过function声明的函数为什么能在定义之前就调用呢?在上一节中我们知道js引擎存在变量提升机制,那么变量提升的本质是什么呢?
本质:执行上下文

2. 什么是执行上下文

执行上下文就是我们写的js代码在执行前js引擎帮我们做的一些“准备工作”,也可以叫做“预处理”。

3. 分类

执行上下文分为:全局执行上下文、函数执行上下文
· 全局执行上下文:只有一个,由浏览器创建,就是window。全局对象window上预定义了大量的方法和属性,我们在全局环境的任意处都能直接访问这些属性方法,同时window对象还是var声明的全局变量的载体。我们通过var创建的全局对象,都可以通过window直接访问。
·函数执行上下文:当某一个函数将要执行之前,便会产生一个函数执行上下文,也就是说函数执行上下文是由代码中函数是否执行决定是否产生。需要注意的是,如果一个函数被调用多次,都会创建一个新的执行上下文。

4. 执行上下文栈

执行上下文的个数为n+1,n代表执行的函数个数,1代表全局执行上下文。执行上下文栈是为了管理这些执行上下文的,从名字可知,是一个栈结构。全局执行上下文处于栈底,当有一个函数即将执行,则该函数的函数执行上下文入栈,函数执行完则出栈。(这里可能一个函数中执行另一个函数 比如递归的形式)

5. 执行上下文结构

执行上下文在创建时完成三件事:1. this是谁 2. 词法环境 3. 变量环境
·在全局执行上下文中,this是window;在函数执行上下文中,谁调用this就是谁,可能为某个对象可能为window可能为undefined(严格检查模式下)
·词法环境分为:全局词法环境 函数词法环境。
·词法环境组成:环境记录+对外部环境引入记录。 环境记录就是当前执行上下文中的定义的let/const/function函数;对外部环境引入记录则是当前环境中是否有外部的引入,对于全局来说为null,对于函数执行上下文来说可能是全局可能是其他函数引入。
·函数词法环境组件:除了有let/const/function等,还会对arguments(伪数组)做预处理
·变量环境:使用var定义的变量就放在变量环境中。(结构与词法环境一致)

6. 总结

  1. 全局执行上下文只有一个,函数执行上下文可以有多个
  2. 执行上下文栈
  3. 使用let/const/function定义的放在词法环境,使用var定义的放在变量环境
  4. 环境记录:在全局中叫对象环境记录;在函数中叫声明性环境记录

7. 补充:let/const到底有没有变量提升?

使用let/const定义的变量存在“暂时性死区”,什么叫暂时性死区?看代码:

console.log(a)
let a = 1

这段代码会报错:大致意思是不能在a初始化之前使用a。
这说明什么问题?在上述记录中可以看到,词法环境是用来保存let/const定义的变量的,也就是说,咱们的let/const定义的变量在声明阶段是有提升的,只是声明提升后没有初始化,状态是uninitialized。即只完成了【声明】。而使用var定义的变量存放在变量环境中,声明做了提升,且初始化为undefined,也就是说var定义的变量做了【声明】+【初始化】的提升。而function定义的函数,是完成了【声明】+【初始化】+【赋值】的。

变量提升总结:
let/const:【声明】状态为: uninitialized 不能在let a = 1之前使用
var : 【声明】+【初始化为undefined】 能在 var a = 1之前使用
function : 【声明】+【初始化】+【赋值】 就是当前函数定义的完整内容
注意:const和let有一个区别,const只有【声明】+【初始化】,没有【赋值】。