Skip to content

js 深入了解执行上下文和执行栈 #20

Open
@muwoo

Description

@muwoo
Owner

执行上下文(Execution Context)

在运行一段 js 代码的时候,就会生成一个可执行的上下文,根据不同类型的区分,可能会存在以下几种类型的代码执行上下文:

  • Global code - 代码第一时间指的全局环境
  • Function code - 函数代码块
  • Eval code - eval 函数包含的代码块

我们来看一段代码:

这段代码中,我们存在一个 global context 和 3 个不同的 function context。你可以随意创建这样的function context但是每一个函数都会创建一个新的执行上下文(EC)。函数外部定义的变量可以被函数内部使用,但是函数内部定义的变量确不能被函数外部使用,这是为什么呢?

执行栈(Execution Context Stack)

浏览器解释器执行 js 是单线程的过程,这就意味着同一时间,只能有一个事情在进行。其他的活动和事件只能排队等候,生成出一个等候队列执行栈(Execution Stack):

我们知道,在一开始执行代码的时候,变确定了一个全局执行上下文global execution context作为默认值。如果在你的全局环境中,调用了其他的函数,程序将会再创建一个新的 EC,然后将此 EC推入进执行栈中execution stack

如果函数内再调用其他函数,相同的步骤将会再次发生:创建一个新的EC -> 把EC推入执行栈。一旦一个EC执行完成,变回从执行栈中推出(pop):

(function foo(i) {
    if (i === 3) {
        return;
    }
    else {
        foo(++i);
    }
}(0));

上面这个函数完成了对自身的3次调用,第一次调用时,生成一个EC推入执行栈,在执行的过程中,又遇到一个新的函数foo(1)此时,又会创建一个新的EC,再次推入...

关于执行栈,有5个关键点可以了解一下:

  • 单线程
  • 同步执行
  • 1 个全局执行上下文 ( Global context)
  • 无线多的 function context
  • 每个函数调用都会创建一个新的 EC,即使是调用自身

执行上下文(EC)的一些细节

从上文了解到了一旦函数被调用,都会创建一个新的执行上下文。但是在 js 解释器中,调用执行上下文有2个阶段:

  1. 创建阶段(函数被调用,但是还没有执行):
  • 创建作用域链 scope chain
  • 定义arguments、变量、函数
  • 确定 this 值
  1. 激活/代码执行阶段
  • 主要是对之前定义的变量分配值,开始解释执行代码。

当每一个 function 被调用时,我们可以大致描述一下会创建这样一个executionContextObj对象

executionContextObj = {
    'scopeChain': { /* 变量对象 + 父执行上下文上的变量对象 */ },
    'variableObject': { /* function arguments /  parameters,内部的 variable 和 function 声明 */ },
    'this': {}
}

活动对象和变量对象(Activation / Variable Object [AO/VO])

executionContextObj在函数被调用的时候创建,在创建阶段,会扫描函数的所有参数和变量。扫描的结果成了executionContextObj内部的variableObject属性。下面是执行步骤的细化:

  1. 发现了一个函数调用
  2. 在执行函数执行,创建了一个 EC,推入执行栈
  3. 进入执行阶段:
  • 初始化作用域链
  • 创建 variableObject:内部包含 arguments 对象;内部定义的函数,以及绑定上对应的变量环境;内部定义的变量
  1. 激活阶段:运行函数内部的代码,对变量复制,代码一行一行的被解释执行.

可以通过一段代码来分析一下:

function foo(i) {
    var a = 'hello';
    var b = function privateB() {

    };
    function c() {

    }
}

foo(22);

当我们执行foo(22)的时候,EC创建阶段会类似生成下面这样的对象:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: undefined,
        b: undefined
    },
    this: { ... }
}

正如你看到的,在创建阶段,会发生属性名称的定义,但是并没有赋值。一旦创建阶段(creation stage)结束,变进入了激活 / 执行阶段,那么fooExecutionContext便会完成赋值,变成这样:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: 'hello',
        b: pointer to function privateB()
    },
    this: { ... }
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @muwoo

        Issue actions

          js 深入了解执行上下文和执行栈 · Issue #20 · muwoo/blogs