资讯专栏INFORMATION COLUMN

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

caozhijian / 990人阅读

摘要:执行上下文作用域链和内部机制一执行上下文执行上下文是代码的执行环境,它包括的值变量对象和函数。创建作用域链一旦可变对象创建完,引擎就开始初始化作用域链。

执行上下文、作用域链和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");

当上面的代码在浏览器中加载时,js引擎先将GEC push入ECS中,当在GEC中调用functionA时,functionA执行上下文被push入栈,并开始执行functionA。

当functionB在functionA中被调用时,functionB的执行上下文被push入栈,开始执行functionB,当functionB中内容执行完,functionB执行上下文被pop出栈,此时栈顶为functionA的执行上下文,继续执行functionA的代码,执行完后pop出栈,栈顶为GEC

最终执行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
}

argumentObject如上所示

函数中的变量会被初始为undefined,参数也会在可变对象中呈现

如果变量在参数对象中已存在,js引擎选择忽略

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引擎进入编译阶段,只处理声明,不处理值。下面走读一遍代码:

a被赋值1,但它并不是个变量或函数声明,js引擎在编译阶段什么都不做;

b变量声明初始化为undefined;

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引擎进入执行阶段并再过一遍。此时将会更新变量名和执行

js引擎发现可变对象中没有a属性,因此在GEC中添加a属性,并初始化为1;

可变对象有b,直接更新b的值为2;

接着是函数声明,不做任何事;

最后调用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

js引擎查看cFunc执行上下文的可变对象没有c,因此添加c,并初始化为undefined,d类似;

a = 3非声明,跳过;

函数声明,创建dFunc属性指向函数的堆空间;

对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的执行阶段

c和d获取到初始化值;

a不是cFunc执行上下文对象中的属性,js引擎会在作用率链的帮助下转到GEC(全局执行上下文),查找a是否在GEC中。如果不存在,则会在当前作用域创建并初始化它;如果GEC中有,则更新其值,这里会更新为3。js引擎只有在发现一个变量在当前执行上下文对象属性中找不到时会跳转到GEC中;

创建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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97581.html

相关文章

  • JS基础知识:变量对象、作用链和闭包

    摘要:前言这段时间一直在消化作用域链和闭包的相关知识。而作用域链则是这套规则这套规则的具体运行。是变量对象的缩写那这样放有什么好处呢我们知道作用域链保证了当前执行环境对符合访问权限的变量和函数的有序访问。 前言:这段时间一直在消化作用域链和闭包的相关知识。之前看《JS高程》和一些技术博客,对于这些概念的论述多多少少不太清楚或者不太完整,包括一些大神的技术文章。这也给我的学习上造成了一些困惑,...

    Keven 评论0 收藏0
  • 【进阶2-1期】深入浅出图解作用链和闭包

    摘要:本期推荐文章从作用域链谈闭包,由于微信不能访问外链,点击阅读原文就可以啦。推荐理由这是一篇译文,深入浅出图解作用域链,一步步深入介绍闭包。作用域链的顶端是全局对象,在全局环境中定义的变量就会绑定到全局对象中。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周开始前端进阶的第二期,本周的主题是作用域闭包,今天是第6天。 本...

    levius 评论0 收藏0
  • JS 作用域链

    摘要:首先,在创建函数时,作用域链内就会先填入对象,图片只例举了全部变量中的一部分。然后,解释器进入函数的执行环境,同样的,首先填入父级的作用域链,就是的,包括了对象活动对象。之后再把的活动对象填入到作用域链最顶部,这就是的作用域链了。 之前学习JS函数部分时,提到了作用域这一节,但是因为使用材料书不同,今天在读博客的时候发现其实还有一个知识点即作用域链,所以来写一些个人理解和认识加深记忆。...

    darry 评论0 收藏0
  • 【进阶2-3期】JavaScript深入之闭包面试题解

    摘要:闭包面试题解由于作用域链机制的影响,闭包只能取得内部函数的最后一个值,这引起的一个副作用就是如果内部函数在一个循环中,那么变量的值始终为最后一个值。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第二期,本周的主题是作用域闭包,今天是第8天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了...

    alanoddsoff 评论0 收藏0
  • 【进阶2-2期】JavaScript深入之从作用域链理解闭包

    摘要:使用上一篇文章的例子来说明下自由变量进阶期深入浅出图解作用域链和闭包访问外部的今天是今天是其中既不是参数,也不是局部变量,所以是自由变量。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第二期,本周的主题是作用域闭包,今天是第7天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计...

    simpleapples 评论0 收藏0

发表评论

0条评论

caozhijian

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<