Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
dark9wesley opened this issue Mar 7, 2021 · 0 comments
Open

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

dark9wesley opened this issue Mar 7, 2021 · 0 comments

Comments

@dark9wesley
Copy link
Owner

dark9wesley commented Mar 7, 2021

执行上下文

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

在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深入之执行上下文

@dark9wesley dark9wesley changed the title javaScript-执行上下文栈与执行上下文 javaScript-执行上下文与执行上下文栈 Mar 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant