JS(执行上下文、函数堆栈、提升)

一、几个概念

  • EC(Execution Context):函数执行环境(或执行上下文)
  • ECS(Execution Context Stack):执行环境栈
  • VO(Variable Object):变量对象
  • AO(Active Object) :活动对象
  • scope chain:作用域链

二、EC(执行上下文)

解释:每次当控制器转到ECMAScript可执行代码的时候,就会进入到一个执行上下文。

三、可执行代码的类型(三种)

  1. 全局级别代码(Global Code)
    这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  2. 函数级别代码(Function Code)
    每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
  3. Eval代码(Eval code)
    执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它
  • 示例(可以将“执行上下文”看做当前代码的运行环境或者作用域。下面我们来看一个示例,其中包括了全局以及函数级别的执行上下文)


    JS(执行上下文、函数堆栈、提升)_第1张图片
    执行上下文.png

上图中,一共用4个执行上下文。紫色的代表全局的上下文;绿色代表person函数内的上下文;蓝色以及橙色代表person函数内的另外两个函数的上下文。注意,不管什么情况下,只存在一个全局的上下文,该上下文能被任何其它的上下文所访问到。也就是说,我们可以在person的上下文中访问到全局上下文中的sayHello变量,当然在函数firstName或者lastName中同样可以访问到该变量。至于函数上下文的个数是没有任何限制的,每到调用执行一个函数时,引擎就会自动新建出一个函数上下文,换句话说,就是新建一个局部作用域,可以在该局部作用域中声明私有变量等,在外部的上下文中是无法直接访问到该局部作用域内的元素的。在上述例子的,内部的函数可以访问到外部上下文中的声明的变量,反之则行不通。

四、ECS(执行上下文堆栈)

用MDN上的一个例子来引入函数执行栈的概念

function foo(i) {
    if (i < 0) return;
    console.log('begin:' + i);
    foo(i - 1);
    console.log('end:' + i);
}
foo(2);

// 输出:

// begin:2
// begin:1
// begin:0
// end:0
// end:1
// end:2
  • 图解


    JS(执行上下文、函数堆栈、提升)_第2张图片
    图解.png
  • 当浏览器首次载入你的脚本,它将默认进入全局执行上下文。如果,你在你的全局代码中调用一个函数,你程序的时序将进入被调用的函数,并创建一个新的执行上下文,并将新创建的上下文压入执行栈的顶部。
    如果你调用当前函数内部的其他函数,相同的事情会在此上演。代码的执行流程进入内部函数,创建一个新的执行上下文并把它压入执行栈的顶部。浏览器总会执行位于栈顶的执行上下文,一旦当前上下文函数执行结束,它将被从栈顶弹出,并将上下文控制权交给当前的栈。这样,堆栈中的上下文就会被依次执行并且弹出堆栈,直到回到全局的上下文。
  • 例子
(function goo(i) {
    console.log(i);
    if (i === 3) {
        return
    } else {
        goo(++i)
    }
})(0)

上述goo被声明后,通过()运算符强制直接运行了。函数代码就是调用了其自身3次,每次是局部变量i增加1。每次goo函数被自身调用时,就会有一个新的执行上下文被创建。每当一个上下文执行完毕,该上下文就被弹出堆栈,回到上一个上下文,直到再次回到全局上下文。整个过程抽象如下图:


JS(执行上下文、函数堆栈、提升)_第3张图片
流程图.png
  • 由此可得执行上下文归纳
    (1)单线程
    (2)同步执行
    (3)唯一的一个全局上下文
    (4)函数的执行上下文的个数没有限制
    (5)每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此

五、VO(变量对象)/AO(活动对象)

AO其实就是被激活的VO,两个其实是一个东西。

  • 变量对象(Variable object):
    是说JS的执行上下文中都有个对象用来存放执行上下文中可被访问但是不能被delete的函数标示符、形参、变量声明等。它们会被挂在这个对象上,对象的属性对应它们的名字对象属性的值对应它们的值但这个对象是规范上或者说是引擎实现上的不可在JS环境中访问到活动对象。
  • 活动对象(Activation object): 有了变量对象存每个上下文中的东西,但是它什么时候能被访问到呢?就是每进入一个执行上下文时,这个执行上下文儿中的变量对象就被激活,也就是该上下文中的函数标示符、形参、变量声明等就可以被访问到了。

六、执行上下文的建立过程(生命周期)

  1. 创建阶段(当函数被调用,但未执行任何其内部代码之前):
    (1)创建作用域链
    (2)创建变量、函数和参数
    (3)求 this 的值

  2. 执行阶段:初始化变量的值和函数的引用,解释/执行代码。

https://blog.csdn.net/hi_kevin/article/details/37761919

https://juejin.im/post/5ba32171f265da0ab719a6d7

https://feclub.cn/post/content/ec_ecs_hosting

https://juejin.im/post/5ab072c3f265da23884ce1b4#heading-0

https://blog.csdn.net/qqchenyufei/article/details/82795713

总结:
  1. EC分为创建执行上下文和执行代码两个阶段
  2. 每个EC可以抽象成一个对象,这个对象具有三个属性,分别为:作用域链(Scope)、VO|AO(只能有一个)和 this

`

你可能感兴趣的:(JS(执行上下文、函数堆栈、提升))