JavaScript基础篇(三):执行上下文和作用域

102 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

执行上下文

什么是执行上下文?

当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。就是对应的执行环境,包括在该执行环境中可以访问的变量对象(VO)、活动对象(AO)、作用域链(Scope)和调用者信息(this)

什么是执行上下文栈?

在执行一段代码时,JavaScript在解释执行时会先创建一个全局上下文globalContext将其压入栈ECStack中,然后再执行一个函数时,会创建一个函数上下文funcContex,将其压入栈中,函数执行完毕,将其弹出,这样就形成了执行上下文栈。

什么是变量对象(VO)?

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明

什么是活动对象(AO)?

活动对象和变量对象是一个东西,变量对象是不可访问的,只有到当执行一个执行上下文中时,这个执行上下文的变量对象才会被激活,就变成了活动对象。这个时候才能访问各种属性。

活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。

什么是作用域链(Scope)?

当查找一个变量时,会从当前执行上下文的变量对象上查找,如果没有查到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

什么是调用者信息(this)?

如果当前函数被作为对象方法调用或使用 bind call applyAPI 进行委托调用,则将当前代码块的调用者信息(this value)存入当前执行上下文,否则默认为全局对象调用。

如何创建一个执行上下文?

什么情况会创建?

  1. 全局代码
  2. 进入函数体代码
  3. eval函数参数指定代码
  4. module代码

创建步骤?

  1. 创建全局执行上下文,并加入栈顶,对于函数上下文会初始化作用域链

  2. 代码分析(预解析)

    • 找到所有非函数的var变量声明
    • 找到所有顶级函数声明
    • 找到所有顶级let、const、class声明
  3. 名称重复处理

    • 扫描上下文找到所有函数声明:

      1. 对于每个找到的函数,用它们的原生函数名,在变量对象中创建一个属性,该属性里存放的是一个指向实际内存地址的指针
      2. 如果函数名称已经存在了,属性的引用指针将会被覆盖
    • 扫描上下文找到所有 var 的变量声明:

      1. 对于每个找到的变量声明,用它们的原生变量名,在变量对象中创建一个属性,并且使用 undefined 来初始化
      2. 如果变量名作为属性在变量对象中已存在,则不做任何处理并接着扫描
  4. 创建绑定

    • 初始化var变量为undefined
    • 函数声明:记录函数名称,并初始化为新创建函数对象
    • 记录let、const、class声明变量,但是不初始化
  5. 执行语句

分析一段例子,来理解一下,以下代码如何执行?

var a = 10;

function foo() {
    console.log(a) // Cannot access 'a' before initialization
    let a;
}

foo()

作用域和作用域链

什么是作用域?

负责收集和维护所有声明的标识符组成的一系列查询,并实施一套规则,确定当前执行代码对这些标识符的权限。通俗点就是据名称来查找变量的一套规则。另外,作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

什么是作用域链?(见上方回答)

作用域和执行上下文区别?

执行上下文在运行时确定,随时可能改变(如:this);作用域在定义时就确定,并且不会改变!