zoukankan      html  css  js  c++  java
  • JS引擎线程的执行过程的三个阶段(一)

    浏览器首先按顺序加载由<script>标签分割的js代码块,加载js代码块完毕后,立刻进入以下三个阶段,然后再按顺序查找下一个代码块,再继续执行以下三个阶段,无论是外部脚本文件(不异步加载)还是内部脚本代码块,都是一样的原理,并且都在同一个全局作用域中。

    JS引擎线程的执行过程的三个阶段:

    • 语法分析
    • 预编译阶段
    • 执行阶段

    一. 语法分析

    分析该js脚本代码块的语法是否正确,如果出现不正确,则向外抛出一个语法错误(SyntaxError),停止该js代码块的执行,然后继续查找并加载下一个代码块;如果语法正确,则进入预编译阶段。

    下面阶段的代码执行不会再进行语法校验,语法分析在代码块加载完毕时统一检验语法。

    二. 预编译阶段

    1. js的运行环境

    • 全局环境(JS代码加载完毕后,进入代码预编译即进入全局环境)

    • 函数环境(函数调用执行时,进入该函数环境,不同的函数则函数环境不同)

    • eval(不建议使用,会有安全,性能等问题)

    每进入一个不同的运行环境都会创建一个相应的执行上下文(Execution Context),那么在一段JS程序中一般都会创建多个执行上下文,js引擎会以栈的方式对这些执行上下文进行处理,形成函数调用栈(call stack),栈底永远是全局执行上下文(Global Execution Context),栈顶则永远是当前执行上下文。

    2. 函数调用栈/执行栈

    调用栈,也叫执行栈,具有LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。

    首次运行JS代码时,会创建一个全局执行上下文并Push到当前的执行栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push到当前执行栈的栈顶。

    当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文控制权将移到当前执行栈的下一个执行上下文。

    var a = 'Hello World!';
    
    function first() {  
      console.log('Inside first function');  
      second();  
      console.log('Again inside first function');  
    }
    
    function second() {  
      console.log('Inside second function');  
    }
    
    first();  
    console.log('Inside Global Execution Context');
    
    // Inside first function
    // Inside second function
    // Again inside first function
    // Inside Global Execution Context
    

      

     

    函数调用栈

    3. 执行上下文的创建

    执行上下文可理解为当前的执行环境,与该运行环境相对应,具体分类如上面所说分为全局执行上下文和函数执行上下文。创建执行上下文的三部曲:

    • 创建变量对象(Variable Object)

    • 建立作用域链(Scope Chain)

    • 确定this的指向

    3.1 创建变量对象

    创建变量对象

    • 创建arguments对象:检查当前上下文中的参数,建立该对象的属性与属性值,仅在函数环境(非箭头函数)中进行,全局环境没有此过程

    • 检查当前上下文的函数声明:按代码顺序查找,将找到的函数提前声明,如果当前上下文的变量对象没有该函数名属性,则在该变量对象以函数名建立一个属性,属性值则为指向该函数所在堆内存地址的引用,如果存在,则会被新的引用覆盖。

    • 检查当前上下文的变量声明:按代码顺序查找,将找到的变量提前声明,如果当前上下文的变量对象没有该变量名属性,则在该变量对象以变量名建立一个属性,属性值为undefined;如果存在,则忽略该变量声明

    函数声明提前和变量声明提升是在创建变量对象中进行的,且函数声明优先级高于变量声明。具体是如何函数和变量声明提前的可以看后面。

    创建变量对象发生在预编译阶段,但尚未进入执行阶段,该变量对象都是不能访问的,因为此时的变量对象中的变量属性尚未赋值,值仍为undefined,只有进入执行阶段,变量对象中的变量属性进行赋值后,变量对象(Variable Object)转为活动对象(Active Object)后,才能进行访问,这个过程就是VO –> AO过程。

    3.2 建立作用域链

    通俗理解,作用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

    可以通过一个例子简单理解:

    var num = 30;
    
    function test() {
        var a = 10;
    
        function innerTest() {
            var b = 20;
    
            return a + b
        }
    
        innerTest()
    }
    
    test()
    

      

     

    在上面的例子中,当执行到调用innerTest函数,进入innerTest函数环境。全局执行上下文和test函数执行上下文已进入执行阶段,innerTest函数执行上下文在预编译阶段创建变量对象,所以他们的活动对象和变量对象分别是AO(global),AO(test)和VO(innerTest),而innerTest的作用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,如下:

    innerTestEC = {
    
        //变量对象
        VO: {b: undefined}, 
    
        //作用域链
        scopeChain: [VO(innerTest), AO(test), AO(global)],  
        
        //this指向
        this: window
    }
    

      

     

    深入理解的话,创建作用域链,也就是创建词法环境,而词法环境有两个组成部分:

    • 环境记录:存储变量和函数声明的实际位置
    • 对外部环境的引用:可以访问其外部词法环境

    词法环境类型伪代码如下:

    // 第一种类型: 全局环境
    GlobalExectionContext = {  // 全局执行上下文
      LexicalEnvironment: {    	  // 词法环境
        EnvironmentRecord: {   		// 环境记录
          Type: "Object",      		   // 全局环境
          // 标识符绑定在这里 
          outer: <null>  	   		   // 对外部环境的引用
      }  
    }
    
    // 第二种类型: 函数环境
    FunctionExectionContext = { // 函数执行上下文
      LexicalEnvironment: {  	  // 词法环境
        EnvironmentRecord: {  		// 环境记录
          Type: "Declarative",  	   // 函数环境
          // 标识符绑定在这里 			  // 对外部环境的引用
          outer: <Global or outer function environment reference>  
      }  
    }
    

      

    在创建变量对象,也就是创建变量环境,而变量环境也是一个词法环境。在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储函数声明和变量( let 和 const )绑定,而后者仅用于存储变量( var )绑定。

    如例子:

    let a = 20;  
    const b = 30;  
    var c;
    
    function multiply(e, f) {  
     var g = 20;  
     return e * f * g;  
    }
    
    c = multiply(20, 30);
    

      

     

    执行上下文如下所示

    GlobalExectionContext = {
    
      ThisBinding: <Global Object>,
    
      LexicalEnvironment: {  
        EnvironmentRecord: {  
          Type: "Object",  
          // 标识符绑定在这里  
          a: < uninitialized >,  
          b: < uninitialized >,  
          multiply: < func >  
        }  
        outer: <null>  
      },
    
      VariableEnvironment: {  
        EnvironmentRecord: {  
          Type: "Object",  
          // 标识符绑定在这里  
          c: undefined,  
        }  
        outer: <null>  
      }  
    }
    
    FunctionExectionContext = {  
       
      ThisBinding: <Global Object>,
    
      LexicalEnvironment: {  
        EnvironmentRecord: {  
          Type: "Declarative",  
          // 标识符绑定在这里  
          Arguments: {0: 20, 1: 30, length: 2},  
        },  
        outer: <GlobalLexicalEnvironment>  
      },
    
      VariableEnvironment: {  
        EnvironmentRecord: {  
          Type: "Declarative",  
          // 标识符绑定在这里  
          g: undefined  
        },  
        outer: <GlobalLexicalEnvironment>  
      }  
    }
    

      

     

    变量提升的具体原因:在创建阶段,函数声明存储在环境中,而变量会被设置为 undefined(在 var 的情况下)或保持未初始化(在 let 和 const 的情况下)。所以这就是为什么可以在声明之前访问 var 定义的变量(尽管是 undefined ),但如果在声明之前访问 let 和 const 定义的变量就会提示引用错误的原因。此时let 和 const处于未初始化状态不能使用,只有进入执行阶段,变量对象中的变量属性进行赋值后,变量对象(Variable Object)转为活动对象(Active Object)后,letconst才能进行访问。

    关于函数声明和变量声明,这篇文章讲的很好:https://github.com/yygmind/blog/issues/13

    另外关于闭包的理解,如例子:

    function foo() {
        var num = 20;
    
        function bar() {
            var result = num + 20;
    
            return result
        }
    
        bar()
    }
    
    foo()
    

      

     

    浏览器分析如下:

    闭包

    chrome浏览器理解闭包是foo,那么按浏览器的标准是如何定义闭包的,总结为三点:

    • 在函数内部定义新函数

    • 新函数访问外层函数的局部变量,即访问外层函数环境的活动对象属性

    • 新函数执行,创建新的函数执行上下文,外层函数即为闭包

    3.3 this指向

    比较复杂,后面专门弄一篇文章来整理。

     

     

    第三个阶段请看  JS引擎线程的执行过程的三个阶段(二)

     

    文章参考:

    https://github.com/yygmind/blog/issues/12

    https://heyingye.github.io/2018/03/19/js引擎的执行过程(一)

    https://heyingye.github.io/2018/03/26/js引擎的执行过程(二)

    https://github.com/yygmind/blog

     

     

  • 相关阅读:
    监控平台
    自动化配置管理
    软件课程设计(15)
    软件课程设计(14)
    软件课程设计(13)
    软件课程设计(12)
    软件课程设计(11)
    软件课程设计(10)
    软件课程设计(9)
    参考资料
  • 原文地址:https://www.cnblogs.com/BoatGina/p/10433518.html
Copyright © 2011-2022 走看看