持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
执行上下文
什么是执行上下文?
当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。就是对应的执行环境,包括在该执行环境中可以访问的变量对象(VO)、活动对象(AO)、作用域链(Scope)和调用者信息(this) 。
什么是执行上下文栈?
在执行一段代码时,JavaScript在解释执行时会先创建一个全局上下文globalContext将其压入栈ECStack中,然后再执行一个函数时,会创建一个函数上下文funcContex,将其压入栈中,函数执行完毕,将其弹出,这样就形成了执行上下文栈。
什么是变量对象(VO)?
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
什么是活动对象(AO)?
活动对象和变量对象是一个东西,变量对象是不可访问的,只有到当执行一个执行上下文中时,这个执行上下文的变量对象才会被激活,就变成了活动对象。这个时候才能访问各种属性。
活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。
什么是作用域链(Scope)?
当查找一个变量时,会从当前执行上下文的变量对象上查找,如果没有查到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
什么是调用者信息(this)?
如果当前函数被作为对象方法调用或使用 bind call apply 等 API 进行委托调用,则将当前代码块的调用者信息(this value)存入当前执行上下文,否则默认为全局对象调用。
如何创建一个执行上下文?
什么情况会创建?
- 全局代码
- 进入函数体代码
- eval函数参数指定代码
- module代码
创建步骤?
-
创建全局执行上下文,并加入栈顶,对于函数上下文会初始化作用域链
-
代码分析(预解析)
- 找到所有非函数的var变量声明
- 找到所有顶级函数声明
- 找到所有顶级let、const、class声明
-
名称重复处理
-
扫描上下文找到所有函数声明:
- 对于每个找到的函数,用它们的原生函数名,在变量对象中创建一个属性,该属性里存放的是一个指向实际内存地址的指针
- 如果函数名称已经存在了,属性的引用指针将会被覆盖
-
扫描上下文找到所有
var的变量声明:- 对于每个找到的变量声明,用它们的原生变量名,在变量对象中创建一个属性,并且使用
undefined来初始化 - 如果变量名作为属性在变量对象中已存在,则不做任何处理并接着扫描
- 对于每个找到的变量声明,用它们的原生变量名,在变量对象中创建一个属性,并且使用
-
-
创建绑定
- 初始化var变量为undefined
- 函数声明:记录函数名称,并初始化为新创建函数对象
- 记录let、const、class声明变量,但是不初始化
-
执行语句
分析一段例子,来理解一下,以下代码如何执行?
var a = 10;
function foo() {
console.log(a) // Cannot access 'a' before initialization
let a;
}
foo()
作用域和作用域链
什么是作用域?
负责收集和维护所有声明的标识符组成的一系列查询,并实施一套规则,确定当前执行代码对这些标识符的权限。通俗点就是据名称来查找变量的一套规则。另外,作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
什么是作用域链?(见上方回答)
作用域和执行上下文区别?
执行上下文在运行时确定,随时可能改变(如:this);作用域在定义时就确定,并且不会改变!