美文网首页
Javascript执行上下文

Javascript执行上下文

作者: 滑天下之大稽 | 来源:发表于2020-02-27 12:03 被阅读0次

    原文章资料:Understanding Execution Context and Execution Stak in Javascript

    什么是 Excution Context

    简单的说,执行上下文是 Javascript 代码计算和被执行环境的抽象概念。Javascript 代码在哪里执行,哪里就是执行上下文。

    Excution Context 的种类

    • 全局执行上下文 —— 最基础的执行上下文,一个运行的 Javascript 程序只存在一个全局执行上下文。写在函数体之外的代码都处在全局执行上下文。他做了两件事情:创建了一个全局对象(浏览器中是window对象,node环境中是global),并把全局对象赋值给 this。
    • 函数执行上下文 —— 每当函数被调用,函数内部就会生成一个新的执行上下文。每个函数都有自己的执行上下文,但是只有当函数被调用的时候,才会被创建。
    • eval 函数执行上下文 —— eval 函数中的代码也有自己的执行上下文,但是 eval 并不常用。

    Excution Stack

    执行栈,又称“调用栈”,是一个先进后出的结构(LIFO, Last in First Out),用来存放代码执行过程中所有的执行上下文。

    Javascript 引擎在执行代码时,创建并把全局执行上下文压入 Excution Stack;每当执行到函数调用的时候,就会创建新的函数执行上下文,然后从栈顶端推入栈中。

    Javascript 引擎从顶端开始执行函数,当函数被执行完成后,它的执行上下文就会被推出栈,然后再去执行栈中的下一个上下文。

    🌰 简单的例子:

    let 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');
    
    图片来自于原博客

    执行上下文是如何创建的

    创建上下文分为两步:

    1. 创建阶段
    2. 执行阶段

    创建阶段:

    LexicalEnvironment:词法环境
    VariableEnvironment:变量环境

    1. 创建词法环境组件
    2. 创建变量环境组件

    Lexical Environment

    A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.

    词法环境是用来定义 基于ECMAScript代码词法嵌套结构的具体变量和函数的关联 的具体类型。词法环境由环境记录器和一个只想外部词法环境的 引用(outer) 组成。

    每一个 词法环境 由以下结构组成:

    1. Environment Record
    2. Reference to the outer environment
    3. This binding
    Environment Record

    存储词法环境中 定义的变量和函数。

    1. 声明式环境记录器存储变量、函数和参数。
    2. 对象环境记录器用来定义出现在全局上下文中的变量和函数,并且存储了全局对象包含的方法和属性。

    对于 函数内部的词法环境,环境记录器还会记录一个 arguments 对象(存储参数的索引和值的键值对)和参数的长度。这个结构看上去如何下:

    function foo(a, b) { var c = a + b }
    // argument object
    Arguments: {0: 2, 1: 3, length: 2}
    
    Reference to the outer environment

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

    this 的绑定

    在全局执行上下文中,this 的值指向全局对象。(在浏览器中,this引用 Window 对象)。

    在函数执行上下文中,this 的值取决于该函数是如何被调用的。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined(在严格模式下)。例如:

    const person = {
      name: 'peter',
      birthYear: 1994,
      calcAge: function() {
        console.log(2018 - this.birthYear);
      }
    }
    person.calcAge(); 
    // 'this' refers to 'person', because 'calcAge' was called with //'person' object reference
    const calculateAge = person.calcAge;
    calculateAge();
    // 'this' refers to the global window object, because no object reference was given
    

    抽象来说,词法环境的结构类似下面代码表示:

    GlobalExectionContext = {
        LexicalEnvironment: {
            EnvironmentRecord: {
                Type: "Object",
                // Identifier bindings go here
            }
            outer: <null>,
            this: <global object>
        }
    }
    FunctionExectionContext = {
        LexicalEnvironment: {
            EnvironmentRecord: {
                Type: "Declarative",
                // Identifier bindings go here
            },
            outer: <Global or outer function environment reference>,
            this: <depends on how function is called>
        }
    }
    

    Variable Environment

    它同样是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。它有着上面定义的词法环境的所有属性。

    在 ES6 中,词法环境组件和变量环境的一个不同就是前者被用来存储函数声明和变量(letconst)绑定,而后者只用来存储 var 变量绑定。

    执行阶段

    在此阶段,将完成对所有这些变量的分配,并最终执行代码。

    例子🌰

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

    当上面的代码执行时,Javascript引擎会创建一个全局执行上下文来执行全局代码。全局上下文创建阶段看上去和这个差不多:

    GlobalExectionContext = {
      LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          // Identifier bindings go here
          a: < uninitialized >,
          b: < uninitialized >,
          multiply: < func >
        }
        outer: <null>,
        ThisBinding: <Global Object>
      },
      VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          // Identifier bindings go here
          c: undefined,
        }
        outer: <null>,
        ThisBinding: <Global Object>
      }
    }
    

    在变量分配在执行阶段完成,全局执行上下文如下:

    GlobalExectionContext = {
        LexicalEnvironment: {
            EnvironmentRecord: {
              Type: "Object",
              // Identifier bindings go here
              a: 20,
              b: 30,
              multiply: < func >
            }
            outer: <null>,
            ThisBinding: <Global Object>
          },
        VariableEnvironment: {
            EnvironmentRecord: {
              Type: "Object",
              // Identifier bindings go here
              c: undefined,
            }
            outer: <null>,
            ThisBinding: <Global Object>
          }
        }
    }
    

    当执行函数 multiply(20, 30),就会创建一个新的函数执行上下文来执行函数代码:

    FunctionExectionContext = {
        LexicalEnvironment: {
            EnvironmentRecord: {
              Type: "Declarative",
              // Identifier bindings go here
              Arguments: {0: 20, 1: 30, length: 2},
            },
            outer: <GlobalLexicalEnvironment>,
            ThisBinding: <Global Object or undefined>,
          },
        VariableEnvironment: {
            EnvironmentRecord: {
              Type: "Declarative",
              // Identifier bindings go here
              g: undefined
            },
            outer: <GlobalLexicalEnvironment>,
            ThisBinding: <Global Object or undefined>
          }
        }
    }
    

    之后执行上下文来到 执行阶段,这意味着已完成对函数内部变量的分配。

    FunctionExectionContext = {
        LexicalEnvironment: {
            EnvironmentRecord: {
              Type: "Declarative",
              // Identifier bindings go here
              Arguments: {0: 20, 1: 30, length: 2},
            },
            outer: <GlobalLexicalEnvironment>,
            ThisBinding: <Global Object or undefined>,
          },
        VariableEnvironment: {
            EnvironmentRecord: {
              Type: "Declarative",
              // Identifier bindings go here
              g: 20
            },
            outer: <GlobalLexicalEnvironment>,
            ThisBinding: <Global Object or undefined>
          }
        }
    }
    

    函数执行之后,返回值存到了变量 c 中,随后全局词法环境被更新了,全局的代码运行完成,程序结束。

    上面我们看到 letconst 声明的变量并没有被初始化,但是 var 定义的变量被设置为 undefined(这对应了 letconst 没有变量提示这一特性)。

    这是因为在创建阶段,Javascript 引擎读取了所有的代码,找到了变量和函数的定义,其中函数声明被完整的存在了环境中,而变量被初始化称 undefined (如果是 var 声明的变量)或者保持未初始化(如果是 letconst 声明的变量)。

    这就是为什么 var 声明的变量可以在声明之前被访问到,而 letconst 声明的变量不允许被访问(reference error)。

    这就是常说的 变量提升(varaible hoisting)

    相关文章

      网友评论

          本文标题:Javascript执行上下文

          本文链接:https://www.haomeiwen.com/subject/rvjthhtx.html