执行上下文
查漏补缺
-
栈:数据结构的一种。先进后出,后进先出。
-
执行栈(调用栈):存储在代码执行期间创建的所有执行上下文,同样具有后进先出的特点。
EC:执行上下文
每当控制器运行到一段可执行代码时,控制器就进入了一个执行上下文,分为三种
- 全局代码:默认代码环境,JS引擎最先进入的环境
- 函数代码:执行一个函数时,函数内部的代码
- Eval代码:在Eval函数内的代码
执行上下文可以看做是一个对象,其中包含了:
- this
- 直接调用函数this指向全局对象
- 在函数外调用 this指向全局对象
- 通过对象调用、new函数,this指向调用对象或new出的新对象
- scope: 作用域链
- VO(variable object):变量对象,该对象包含
arguments
对象- 参数
- 内部变量
- 函数声明
EC = {
this: {}
VO: { arguments, 参数, 变量, 函数声明 },
Scope: { VO,所有父级执行上下文的VO }
}
执行上下文栈(ECS)
- 全局执行上下文:所有JS执行前,必须有该环境。
- JS引擎始终执行执行上下文栈 栈顶的 执行上下文
- 栈顶是当前执行上下文
-
JS引擎首先进入全局执行上下文,导致全局执行上下文在执行上下文栈的底部(栈底)
-
当在全局上下文中调用函数则会进入一个函数执行上下文,该函数执行上下文就会被压入执行栈的顶部,执行过程中遇到新的函数调用则又压入新的函数的执行上下文。(JS引擎总会执行栈顶的上下文,所以每压入一个新的执行上下文就会打断原来的执行上下文,并且开始执行新的执行上下文的内容)
-
当前函数上下文执行完毕后,会被弹出执行上下文栈,然后继续之前打断的执行期上下文继续执行。
即使是递归函数,每次进入也都会创建一个新的执行上下文,但是函数对象只有一个所以
[[scope]]
只有一个
执行上下文栈的操作类似于数组的push和pop搭配操作,最后一个push进入数组的数据的总是第一个被pop出数组
变量对象 VO(variable object)
每一个执行上下文都包含一个VO,VO可分为两种
- GO(Global object):全局变量对象
- AO(Active object):当前正在活跃(执行)的上下文中的VO
执行上下文的建立
执行上下文的建立分为两个步骤,进入执行上下文 以及 执行阶段
- 进入执行上下文
进入执行上下文后按顺序执行以下步骤:
- 确定所有形参的值(即将实参的值赋值给形参的同名变量),若未传参则设为undefined,确定arguments的值。
- 确定通过字面量声明的函数,将该变量指向函数对象(若VO中有同名变量则覆盖该变量)
- 变量声明:将在函数内定义的变量(通过var声明的变量)值设为undefined(如var a = 1; 拆分为var a = undefined;先进行执行,然后把a = 1;语句等待执行阶段依次执行到该语句后赋值若VO中有同名变量则不会影响该变量)
- 执行阶段
依次执行代码,将一些有赋值的属性进行赋值。若执行上下文时存执行到某个不存在的属性,则会从之前打断的上下文中进行查找
- 举个例子
console.log(a, b); // f a() {}, undefined
var a = 1;
var b = 2;
console.log(a, b); // 1, 2
function a () {};
var b = function() {};
console.log(a, b); // 1, f(){}
- 进入执行上下文,未执行
- 函数声明:将a 的值设置为 a函数 ,(字面量声明函数后,该声明语句在执行阶段可忽略)
- 变量声明:b的值设为undfined
- 此时a的值为函数a,b的值为undefined
- 执行阶段
- 输出a (值为函数a) 输出b(值为undefined)
- 将1赋值给a,将2赋值给b
- 输出a(值为1),输出b (值为2)
- 将匿名函数赋值给遍历b
- 输出a(值为1),输出b(值为匿名函数)
function test(a, b, c, d) {
console.log(a, b, c, d);
// 10, 20, ƒ c() {}, undefined
var a = 1;
var b = function () {}
function c() {}
console.log(a, b, c, d);
// 1, ƒ () {}, ƒ c() {} , undefined
}
test(10, 20, 30)
- 进入函数执行上下文,未执行
- 确定形参的值,将AO内创建变量a,b,c,d。且a值设置为10,b的值设置为20, c的值为30,未传入的d为undefine(此时arguments内有传入实参的三个值,d没有实参不在arguments内)
- 函数声明:将c的值设置为c函数 覆盖原有的c(值为30的c)(字面量声明函数后,该声明语句在执行阶段可忽略)
- 变量声明:此时AO内已经存在有a,b的值,所以a不会设置为undefined,b也不会设置为函数对象
- 此时a的值为10,b的值为20,c的值为函数c,d的值为undefined
- 函数执行阶段
- 输出a (值为函数10) 输出b(值为20),输出c(函数对象c),输出d(undefined)
- 将1赋值给a,将匿名函数赋值给b
- 输出a(值为1),输出b (值为匿名函数),输出c(函数对象c),输出d(undefined)
即使变量在不会执行的代码块内定义(条件不成立的if,while等内)也同样会有变量提示,值为undefined而不会报错
立即执行函数若不为匿名函数,也不会出现在VO中
作用域链 scope
作用域链在函数创建时就已经存在
-
VO中包含一个属性,该属性指向创建该VO的函数本身,全局上下文中没有(即全局代码中执行了A函数创建了函数A的执行上下文,A的执行上下文的VO内含有一个属性指向函数A,该属性目的为了作用域链可以往上查找到上一个函数的
[[scope]]
从而访问上一个执行上下文的VO) -
函数创建时会有一个属性
[[scope]]
,该属性指向创建该函数时的执行上下文的AO(即指向创建新的执行上下文时被打断的执行上下文的AO),创建完新的执行上下文后,AO会变为当前新建的指向上下文的VO。[[scope]]
的值是静态的不会改变的 -
当访问一个变量时,先查找自身VO中是否存在,如果不存在,则依次查找
[[scope]]
属性
[[scope]]
指向上一个被打断的指向上下文的VO,依次往上直到GO -
简而言之,Scope内包含当前AO以及
[[scope]]
。而[[scope]]
则指向上一个被打断的上下文的VO(里面也包含上一个上下文的[[scope]]
)