[译]执行上下文、作用域链和JS内部机制

news/2024/7/6 1:37:31

执行上下文、作用域链和JS内部机制(Execution context, Scope chain and JavaScript internals)

一、执行上下文

执行上下文(Execution context EC)是js代码的执行环境,它包括 this的值、变量、对象和函数。

js执行上下文有3种类型

1. 全局执行上下文(Global execution context GEC)

全局上下文是文件第一次加载到浏览器,js代码开始执行的默认执行上下文。在浏览器环境中,严格模式下this的值为undefined,否则this的值为window对象。GEC只能有一个(因为js执行的全局环境只能有一个)。

2. 函数执行上下文(Functional execution context FEC)

函数执行时创建函数执行上下文,每个函数都有自己的执行上下文。FEC可以获取到GEC中的内容。当在全局上下文中执行代码时js引擎发现一个函数调用,则创建一个函数执行上下文。

3. Eval

执行eval时创建

二、执行上下文栈

执行上下文栈Execution context stack (ECS)是执行js代码时创建的执行栈结构。 GEC默认在栈的最里层,当js引擎发现一个函数调用,则创建这个函数的 FEC并push进栈,js引擎执行栈顶上下文关联的函数,一旦函数执行完,则将其 FEC pop出栈,并往下执行。

看个例子(动图插不了栈动图链接)

var a = 10;

function functionA() {

    console.log("Start function A");

    function functionB(){
        console.log("In function B");
    }

    functionB();

}

functionA();

console.log("GlobalContext");
  1. 当上面的代码在浏览器中加载时,js引擎先将GEC push入ECS中,当在GEC中调用functionA时,functionA执行上下文被push入栈,并开始执行functionA。
  2. 当functionB在functionA中被调用时,functionB的执行上下文被push入栈,开始执行functionB,当functionB中内容执行完,functionB执行上下文被pop出栈,此时栈顶为functionA的执行上下文,继续执行functionA的代码,执行完后pop出栈,栈顶为GEC
  3. 最终执行GEC中代码,执行完pop整个代码结束。

上面讨论了js引擎如何处理执行上下文(push和pop),下面讨论js引擎如何创建执行上下文,这个过程分为两个阶段:创建阶段和执行阶段

三、创建执行上下文

1. 创建阶段(后面又叫编译阶段)

js引擎调用函数,但函数还没开始执行阶段。

js引擎在这个阶段对整个函数进行一个编译(compile the code),主要干了下面三件事:

(1) 创建Activation object 或 the variable object(后面就简称它可变对象吧,不知道有没有专业的中文名)

可变对象是包含所有变量、函数参数和内部函数声明信息的特殊对象,它是一个特殊对象且没有__proto__属性。

(2)创建作用域链

一旦可变对象创建完,js引擎就开始初始化作用域链。作用域链是一个当前函数所在的可变对象的列表,其中包括GEC的可变对象和当前函数的可变对象。

(3)决定this的值

初始化this的值

下面通过一个例子进行说明

function funA (a, b) {
  var c = 3;
  
  var d = 2;
  
  d = function() {
    return a - b;
  }
}


funA(3, 2);

当调用funA和执行funA前的这段时间,js引擎为funA创建了一个executionContextObj如下

executionContextObj = {
 variableObject: {}, // All the variable, arguments and inner function details of the funA
 scopechain: [], // List of all the scopes inside which the current function is
 this // Value of this 
}

可变对象包含参数对象(包含函数参数的细节),声明的变量和函数,如下所示

variableObject = {
  argumentObject : {
    0: a,
    1: b,
    length: 2
  },
  a: 3,
  b: 2
  c: undefined,
  d: undefined then pointer to the function defintion of d
}
  1. argumentObject如上所示
  2. 函数中的变量会被初始为undefined,参数也会在可变对象中呈现
  3. 如果变量在参数对象中已存在,js引擎选择忽略
  4. js引擎在当前函数中遇到函数定义,会用函数名创建一个属性指向函数定义存储的堆内容

2. 执行阶段

在此阶段,js引擎会重扫一遍函数,用具体的变量的值来更新 可变对象,并执行代码内容。

执行阶段执行完后,可变对象的值如下:

variableObject = {
  argumentObject : {
    0: a,
    1: b,
    length: 2
  },
  a: 3,
  b: 2,
  c: 3,
  d: undefined then pointer to the function defintion of d
}

四、完整的例子

代码如下

a = 1;

var b = 2;

cFunc = function(e) {
  var c = 10;
  var d = 15;
  
  a = 3
  
  function dFunc() {
    var f = 5;
  }
  
  dFunc();
}

cFunc(10);

全局编译阶段

当浏览器加载上面的代码后,js引擎进入编译阶段,只处理声明,不处理值。下面走读一遍代码:

  1. a被赋值1,但它并不是个变量或函数声明,js引擎在编译阶段什么都不做;
  2. b变量声明初始化为undefined;
  3. cFunc函数声明初始化为undefined。

此时的

globalExecutionContextObj = {
  variableObject: { // 原文中有时用activationObj
      argumentObj : {
          length:0
      },
      b: undefined,
      cFunc: Pointer to the function definition
  },
  scopeChain: [GLobal execution context variable object],
  this: value of this
}

全局执行阶段

再接着上面,js引擎进入执行阶段并再过一遍。此时将会更新变量名和执行

  1. js引擎发现可变对象中没有a属性,因此在GEC中添加a属性,并初始化为1;
  2. 可变对象有b,直接更新b的值为2;
  3. 接着是函数声明,不做任何事;
  4. 最后调用cFunc,js引擎再次进入编译阶段创建一个cFunc的执行上下文。

此时

globalExecutionContextObj = {
  variableObject: {
      argumentObj : {
          length:0
      },
      b: 2,
      cFunc: Pointer to the function definition,
      a: 1
  },
  scopeChain: [GLobal execution context variable object],
  this: value of this
}

cFunc的编译阶段

由于cFunc有个参数e,js引擎会在cFunc执行上下文对象可变对象添加e属性,并初始化为2

  1. js引擎查看cFunc执行上下文的可变对象没有c,因此添加c,并初始化为undefined,d类似;
  2. a = 3非声明,跳过;
  3. 函数声明,创建dFunc属性指向函数的堆空间;
  4. 对dFunc执行语句忽略

此时

cFuncExecutionContextObj = {
  activationbj: {
      argumentObj : {
          0: e,
          length:1
      },
      e: 10,
      c: undefined,
      d: undefined
      dFunc: Pointer to the function definition,
  },
  scopeChain: [cFunc variable object, Global exection context variable object],
  this: value of this
}

cFunc的执行阶段

  1. c和d获取到初始化值;
  2. a不是cFunc执行上下文对象中的属性,js引擎会在作用率链的帮助下转到GEC(全局执行上下文),查找a是否在GEC中。如果不存在,则会在当前作用域创建并初始化它;如果GEC中有,则更新其值,这里会更新为3。js引擎只有在发现一个变量在当前执行上下文对象属性中找不到时会跳转到GEC中;
  3. 创建dFunc属性并指向函数的堆内存
cFuncExecutionContextObj = {
  activationbj: {
      argumentObj : {
          0: e,
          length:1
      },
      e: 10,
      c: 10,
      d: 15
      dFunc: Pointer to the function definition,
  },
  scopeChain: [cFunc variable object, Global exection context variable object],
  this: value of this
}

调用dFunc,js引擎再次进入编译阶段,创建dFunc执行上下文对象。
dFunc执行上下文对象可以访问到cFunc和全局作用域中的所有变量和函数;同样cFunc可以访问到全局的,但不能访问dFunc中的;全局上下文对象不能访问cFunc和dFunc中的变量和对象。
有了上面的概念,对hoisting(变量提升)应该更容易理解了。

五、作用域链

作用域链是当前函数所在的可变对象列表

看下面一段代码

a = 1;

var b = 2;

cFunc = function(e) {
  var c = 10;
  var d = 15;
  
  console.log(c);
  console.log(a); 
  
  function dFunc() {
    var f = 5;
    console.log(f)
    console.log(c);
    console.log(a); 
  }
  
  dFunc();
}

cFunc(10);

当cFunc被调用时,cFunc的作用域链如下

Scope chain of cFunc = [ cFunc variable object, 
                     Global Execution Context variable object]

当dFunc被调用时,dFunc在cFunc中,dFunc的作用域链包含dFunc、cFunc和全局可变对象

Scope chain of dFunc = [dFunc variable object, 
                    cFunc variable object,
                    Global execution context variable object]

当我们尝试访问dFunc中的f,js引擎查看f是否可从dFunc的可变对象中获取,找到console输出;
访问c变量,js引擎首先在dFunc的可变对象中获取,不能获取,则到cFunc的可变对象中去获取,找到console输出;
访问a变量,同上,最后找到GEC的可变对象,获取到并console输出

同样,cFunc中获取c和a类似

在cFunc中访问不到f变量,但dFunc中可以通过作用域链获取到c和d


http://www.niftyadmin.cn/n/4022861.html

相关文章

linux的tomcat服务器上部署项目的方法

在tomcat服务器上部署项目的前提,是我们已经准备好了tomcat服务器。在CentOs环境下部署JavaWeb环境,部署tomcat服务器在前面的文章中已经总结过了,可以参考以前文章。 一 tomcat服务器修改端口 tomcat服务器配置好以后,默认是8…

ueditor的简单配置和使用

在项目中需要使用到富文本编辑器,我们选用的是ueditor,这是由百度web前端研发部开发所见即所得富文本web编辑器,功能比较强大,可以完成文本的编辑,图片的上传等功能。本文对ueditor的配置使用做一个简单的介绍。 一 准…

人脸识别技术应用场景解析

人脸识别作为一项互联网领域热门的技术,在互联网产品很多领域都有着广泛的应用。下面将对人脸识别的三种技术及提供的产品服务进行讲解分析。 1、人脸检测 场景1:人脸属性识别 人脸属性识别指的是对于识别出来的人脸图像区域再进行分析处理,得…

[POI2007]EGZ-Driving Exam

能到达所有路的充要条件是能到达左右两端的路 用vector反向建边对每条路左右分别求个最长不上升子序列 预处理出每条路向左向右分别需要多建多少路才能到达最左端和最右端 然后跑个\(\Theta(n)\)的尺取法就可以了 本题最长不上升子序列用vectorzkw线段树比二分更加好想&#xf…

linux静态ip的设置

我们经常使用虚拟机安装(我使用的linux版本是CentOS6.5),然后配置服务器的web环境,用于程序的调试。默认情况下,linux使用动态ip,每次启动linux时,它的ip地址都有可能发生变化,为了调…

Dell7040mt安装win7系统说明

几天新买的Dell7040mt收到了,机器预装了win10系统,把win10作为开发平台,可能会有一些问题,所以改为win7,今天折腾了一天,终于把win7系统装上了。总结一下安装的步骤。 1 准备启动盘 一开始准备了uefi的启…

ios-UISwitch

let mySwitch:UISwitch UISwitch(frame: CGRect(x:0,y:80,witdh:10,height:10) //添加触发事件 mySwitch.addTarget(self,action:Selector("SwitchClick:")),forControlEvents:UIControlEvents.valueChanged) //设置滑块的颜色 myswitch.thumbTintcolor UIColor.re…

python 获取脚本所在目录的正确方法

2019独角兽企业重金招聘Python工程师标准>>> 1. 以前的方法 如果是要获得程序运行的当前目录所在位置,那么可以使用os模块的os.getcwd()函数。 如果是要获得当前执行的脚本的所在目录位置,那么需要使用sys模块的sys.path[0]变量或者sys.argv[…