前言

前面一篇文章写了关于声明提升和作用域在结论层面的一些理解,应该说仅仅是知其然而不知其所以然。这一篇文章让我们从JavaScript的执行上下文原理层面来探讨代码的执行机制。

结合思维导图服用更佳理解执行上下文内部机制 (1).png

执行上下文

执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。代码在执行过程中,会创建对应的上下文。

前面我们说到作用域包括全局作用域、函数作用域、eval作用域。而从类型上区分,执行上下文同样包括全局上下文、函数上下文、eval上下文。同样的,eval上下文此处不做讲解。

从阶段层面划分,执行上下文有创建阶段执行阶段。这个才是我们要探讨的重点。

创建阶段

无论是全局上下文,还是函数执行上下文,在创建阶段都会做以下三件事情:创建词法环境组件、创建变量环境组件、确定This指向

创建词法环境组件

词法环境是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符和具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成。

上面这段为ES6官方文档的解释,依照概念,我们把词法环境组件定义为lexicalEnviroment对象,包括环境记录器外部环境引用

环境记录器是存储变量和函数声明的实际位置,准确来说在全局上下文时此处只做letconst的初始化,函数声明提升。而在函数执行上下文时还需多增加一个函数参数的存储

外部环境的引用意味着它可以访问其父级词法环境(作用域)

举个例子:

 let a = 1;
 const b = 2;
 function foo(e,f){
 let c = 3;
 const d = 4;
 }
 foo(1,2);

则这段代码对应执行上下文在创建阶段创建的词法环境组件是这样子的

globalContext = {
    lexicalEnviroment:{
      enviromentRecord:{    
       a:<uninitalized> ,
       b:<uninitalized>,
       foo:<func>
     },
    outer:null
  }
 }
 
FunctionContext = {
   lexicalEnviroment:{
      enviromentRecord:{    
        c:<uninitalized> ,
        d:<uninitalized>,
        arguments:{
          0:1,
          1:2,
          length:2
        }
      },
    outer:globalText
} 

声明提升和作用域的相关内容在第一篇文章已经讲过了,此处不再赘述。JavaScript基础一:声明提升与作用域

创建变量环境组件

在执行上下文的创建阶段,变量环境组件主要是针对var关键定义的变量做一次初始化,即我们常提到的变量声明提升。

 console.log(a);  // undefined
 var a = 1; 
 function foo(){
  var b = 2;
 }
 foo();

则变量环境组件的数据结构如下

 globalContext = {
   variableEnviroment:{
     a:undefined
   }
 }
 
 FunctionContext = {
   variableEnviroment:{
     b:undefined
   }
 }

确定This指向

这个过程也可以参考我的上一篇文章JavaScript基础二:理解执行栈与This指向

创建阶段的知识讲完了,让我们用一段代码来梳理下以上知识点

 let a = 1;
 const b = 2;
 var c = 3;
 function foo(e,f){
   var d = 4;
   console.log(e);
 }
 foo(2,3);

首先这里存在全局执行上下文和函数执行上下文,无论是全局执行上下文还是函数执行上下文,都要做创建词法环境组件、创建变量环境组件、确定This指向这三件事情。

词法环境组件包括环境记录器和外部引用。环境记录器要做的事情是扫描letconst定义的变量,函数声明提升、函数参数存储(仅在函数执行上下文中做)

globalText = {
 lexicalEnviroment:{
   a:<uninitalized>,
   b:<uninitalized>,
   foo:<func>,
   outer:null
 }
}

而创建变量环境组件时主要做var关键字定义的变量声明提升,这一步比较简单。全局执行上下文的This指向到window。

globalText = {
 lexicalEnviroment:{
   a:<uninitalized>,
   b:<uninitalized>,
   foo:<func>,
   outer:null
 },
 variableEnviroment:{
   c:undefined
 },
 ThisBinding:window
 }

同理我们可以写出函数执行上下文在创建阶段所做的事情,注意在函数执行上下文中创建词法环境组件还多做一件事:函数参数的存储

 globalText = {
   lexicalEnviroment:{
     a:<uninitalized>,
     b:<uninitalized>,
     foo:<func>,
     outer:null
   },
  variableEnviroment:{
     c:undefined
  },
  ThisBinding:window
 }
   
 FunctionText = {
   lexicalEnviroment:{
     arguments:{
      0:2,
      1:3,
      length:2
     }
     outer:globalContext
   },
   variableEnviroment:{
     d:undefined
   },
   ThisBinding:window
  }

执行阶段

相比起创建阶段,执行阶段做的内容就简单很多了,直接做一些赋值就可以了。此处不再做赘述。

小结

  1. 执行上下文包括创建阶段和执行阶段,创建阶段主要做三件事情:创建词法环境组件,创建变量环境组件,确定This指向
  2. 词法环境组件包括环境记录器外部引用,外部引用也可以通俗认为是作用域
  3. 环境记录器包括letconst声明的变量初始化,函数声明提升、函数参数。
参考文章:https://juejin.im/post/5ba321...

wuquan133
26 声望1 粉丝

正在成长中的小前端