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

     

     

  • 相关阅读:
    Python 学习笔记 11.模块(Module)
    Python 学习笔记 8.引用(Reference)
    Python 学习笔记 9.函数(Function)
    Python 学习笔记 6.List和Tuple
    Python 学习笔记 4.if 表达式
    Python 学习笔记 2.自省
    Python 学习笔记 3.简单类型
    Python 学习笔记 7.Dictionary
    Python 学习笔记 5.对象驻留
    Python 学习笔记 10.类(Class)
  • 原文地址:https://www.cnblogs.com/BoatGina/p/10433518.html
Copyright © 2011-2022 走看看