Skip to content

javaScript-执行上下文与执行上下文栈 #4

Open
@dark9wesley

Description

@dark9wesley

执行上下文

执行上下文,简单理解就是代码执行的环境。

在JS中,有三种执行上下文,分别是全局执行上下文、函数执行上下文,eval执行上下文(不作了解)。

首先要理解的是,JS的代码不是一行一行执行,而是一段一段的执行。每段也就是一个执行上下文。

每个上下文在开始执行前,都会做一个准备工作,这个工作有三个重要的属性,分别是:变量对象,作用域链,以及this。

变量对象

变量对象是与执行上下文相关的数据作用域,保存了上下文中所有以var关键字声明的变量以及函数声明。

由于全局上下文与函数上下文中的变量对象不尽相同,所以下面分类讨论。

全局变量对象

首先下结论,全局中的变量对象就是全局对象。

在客户端javaScript中,这个全局对象就是window。

全局对象是一个预定义的对象,保存了一堆预定义的属性和方法。

所有在全局上下文中以var关键字声明的变量以及函数声明都会保存在全局对象中。

全局对象是作用域链的顶层,

函数变量对象

在函数执行上下文中,用活动对象代指变量对象。

这两个其实是一个东西,区别是变量对象我们是不可以直接访问的。

当进入一个执行上下文后,变量对象被激活,转换为活动对象,其上的属性和方法,我们才可以进行访问。

函数执行上下文中的活动对象通过函数的Arguments对象进行初始化。

执行过程

执行上下文的代码分为两个阶段进行处理: 分析和执行,我们也可以叫做:

  1. 进入执行上下文
  2. 执行代码

下面分析这两个过程中变量对象的情况。

进入执行上下文

  1. 激活变量对象,通过Arguments对象初始化活动对象(如果是函数执行上下文)

    • 键名为形参,键值为对应的实参。
    • 如果没有对应的实参,键值为undefined。
  2. 上下文中所有函数声明

    • 所有函数声明都会加入变量对象。
    • 键名为函数名,键值为函数体。
    • 如果变量对象中有重名的键,对其进行覆盖。
  3. 上下文中所有以var关键字声明的变量

    • 所有以var关键字声明的变量都会加入变量对象。
    • 键名为变量名,键值为undefind。
    • 如果变量对象中有重名的键,什么都不做。

举个例子:

  function foo(a){
    var b = 2;
    function c(){};
    var d = function(){};

    b = 3;
  }

  foo(1);

进入执行上下文时,活动对象的情况如下:

  AO = {
    arguments: {
      0: 1,
      length: 1,
    },
    a: 1,
    b: 2,
    c: reference to function c(){},
    d: reference to FunctionExpression "d",
  }

执行代码

在代码执行阶段,会顺序执行代码,变量对象会根据代码修改自己的值。

还是上面的例子,代码完毕,活动对象情况如下:

AO = {
    arguments: {
      0: 1,
      length: 1,
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined,
  }

到此变量对象的整个创建过程就结束了,总结一下:

  1. 全局上下文中的变量对象就是全局对象。
  2. 函数执行上下文的初始化只包括Argumens对象。
  3. 在进入执行上下文时,变量对象会添加形参,函数声明,以及以var关键字声明的变量。
  4. 在代码执行阶段,变量对象会根据代码对值进行修改。

作用域链

当想获取一个属性或方法时,会首先从当前上下文的变量对象中查找,如果没有,再去父级上下文的变量对象中查找,一直找到全局的变量对象,也就是全局对象。如果还找不到,就会报错。

这样多个变量对象组成的链表,我们称之为作用域链。

下面以一个函数的创建和调用两个时期,描述作用域链是如何变化的。

函数创建

首先要知道的是,JS中采取的是词法作用域。

也就是说,在函数创建时,它所有的父级变量对象已经决定好了,这一点不会随着它调用的位置而发生改变。

在函数的内部,有个[[scope]]属性。在函数创建时,这个属性会保存所有父级变量对象组成的作用域。

注意:[[scope]]属性不代表完整的作用域链!

举个🌰 :

function foo() {
    function bar() {
        ...
    }
}

函数创建时,各自的[[scope]]为:

foo.[[scope]] = [
 globalContext.VO
]

bar.[[scope]] = [
 fooContext.AO,
 globalContext.VO,
]

函数调用

函数调用,进入一个函数执行上下文,创建完变量对象后,会将变量对象添加到作用域链的前端。

Scope = [AO].concat([[scope]])

至此,作用域链创建完毕。

this

深入了解this

执行上下文栈

在我们写的代码中,会有不止一个函数,也就是说,会有很多个函数执行上下文被创建和执行。

为了统一管理执行上下文, JavaScript 引擎会创建一个执行上下文栈来统一进行管理。

执行上下文栈是一种先入后出的结构,下面简单阐述一下它的运行情况。

  1. 当JS解释器开始执行代码时,最先遇到的就是全局代码,所以会先向执行上下文栈中压入一个全局执行上下文。这个全局执行上下文只有在整个应用程序结束,或者页面被关闭时,才会被弹出栈。
  2. 当一个函数被调用时,就会创建一个函数执行上下文,并把它压入栈中。当函数执行结束,再把它弹出栈。

参考链接

JavaScript深入之词法作用域和动态作用域

JavaScript深入之执行上下文栈

JavaScript深入之变量对象

JavaScript深入之作用域链

JavaScript深入之执行上下文

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions