JavaScript进阶笔记(一):执行上下文和执行栈
JavaScript进阶笔记(一):执行上下文和执行栈
镇长一、执行上下文
1.1 定义
执行上下文是 JS 代码解释和执行时所处的抽象环境。
1.2 种类
- 全局执行上下文:只有一个,浏览器中指定是 window 对象,this 指向这个对象。
- 函数执行上下文:无数个,每次函数被调用都会创建一个。
- Eval函数执行上下文:不常用,很少见。
二、执行栈
执行栈,也叫调用栈,具有后进先出的特点,用来存储程序执行过程中创建的所有的执行上下文。
在程序初次执行时,首先创建全局执行上下文 Push 到栈中。之后每次发生调用 JS 引擎都会创建新的函数执行上下文并 Push 到执行栈的栈顶。当栈顶的函数执行完成后,相应的执行上下文会被 Pop 出栈,继续执行下一个执行上下文。
关于调用栈有五个点需要清楚:
- 执行是单线程的。
- 同步执行。
- 只有一个全局上下文,永远在栈底。
- 函数上下文,不唯一。
- 每次调用函数都会创建新的上下文,包括调用自身。
三、执行上下文的创建
上下文的创建分为两个阶段:创建阶段 和 执行阶段
3.1 创建阶段(指的是函数被调用,但是函数内部还没执行的阶段)
创建阶段:绑定 this,词法环境,变量环境。
1 | ExecutionContext = { |
this 绑定,在全局上下文中,this 指向全局对象,浏览器中 this 指向 window 对象,而在 node 中指向 module 对象。函数上下文中,this 指向取决于调用方式。具体有:默认绑定、隐式绑定、显式绑定(硬绑定)、new 绑定、箭头函数。
3.1.1 词法环境
- 环境记录:存储变量和函数声明的实际位置。
- 对外部环境的引用:访问外部词法环境。
对于全局环境,没有外部环境所以为 null。有一个全局对象 window 及其关联的方法和属性以及用户定义的变量。this 指向这个对象。对于函数环境,外部环境是全局环境或者包含内部函数的外部函数环境。存储用户定义的变量,包括 arguments 对象。
3.1.2 变量环境
变量环境是特殊的词法环境。词法环境用于存储函数声明和变量(let和const) 绑定,而变量环境仅存储变量(var) 绑定。
使用例子进行介绍
1 | let a = 20; |
执行上下文如下所示
1 | // 全局执行上下文 |
变量提升:创建阶段,函数声明存储在环境中,var声明的变量被设置为 undefined,而 let 和 const 声明的变量设置为未初始化。所以可以在声明前访问 var 定义的变量,但不可以在声明前访问 let 和 const 定义的变量。
3.2 执行阶段
完成对变量的分配,并执行。
四、执行上下文的应用
4.1 变量提升和函数提升
变量提升的原理:var 声明的变量是存储在执行上下文的变量环境中,并且默认是 undefined 。
函数声明的优先级高于变量。同一作用域下存在多个同名函数声明,后面的会替换前面的函数声明。
4.2 测验
两段代码的不同点,输出结果相同。
1 | // 代码一 |
答案是:调用栈不同
代码一的调用栈顺序:
1 | Stack.push(<checkscope>, functionContext); |
代码二的调用栈顺序:
1 | Stack.push(<checkscop>, functionContext); |
五、函数上下文
函数上下文中,用活动对象来(AO)来表示变量对象。也就说活动对象和变量对象是一回事,只是叫法和所处的位置不同。变量对象是 JavaScript 规范中的叫法,进入执行上下文变量被激活,此时称为活动对象(AO)。
活动对象在进入函数上下文时被创建,通过 Arguments 属性初始化。
5.1 执行过程
执行上下文分为两个阶段:进入执行上下文和代码执行。
进入和执行阶段都做了什么以及 AO 的状态。
5.1.1 进入阶段
进入阶段没有执行代码,此时的变量对象:
- 函数的所有形参:没有实参,属性值为 undefined。
- 函数声明
- 变量声明
1 | function foo(a) { |
此时的AO:
1 | AO = { |
形参 arguments 已经赋值,但是变量还只是 undefined 。
5.1.2 执行阶段
1 | AO = { |
总结如下:
- 全局上下文的变量对象初始化是全局对象
- 函数上下文的变量对象初始化只包括 Arguments 对象
- 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
- 在代码执行阶段,会再次修改变量对象的属性值