zoukankan      html  css  js  c++  java
  • javascript执行上下文和变量对象

    执行上下文(execution context):

    执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。

    js语言是一段一段的顺序执行,这个“段”其实就是我们说的这个执行上下文,分为:全局执行上下文,函数执行上下文,Eval函数执行上下文(很少用)

    执行上下文由以下几个属性构成:

    executionContext:{

      variable objects:var、function[、arguments]

      scope chain:variable objects + all parents scope

      thisValue:content object

    }

    执行上下文的代码分为两个阶段:

    1. 进入执行上下文
    2. 代码执行

    进入执行上下文后初始化的规则如下(且按如下顺序执行):

    1. 函数的所有形参(这一条是在函数上下文中才用到):
      • 由名称和对应值组成的一个变量对象的属性被创建
      • 如果没有传实参,属性值将置为undefined
    2. 函数声明
      • 由名称和该函数体组成的一个变量对象的属性被创建
      • 如果有两个同名函数声明,后者会替换前者
    3. 变量声明:
      • 由名称和undefined组成的一个变量对象的属性被创建
      • 如果变量名称和已经声明的形参或者是函数名相同,则变量声明不会替代已经存在的这类属性
            function test() {
                console.log(a);    // a is not defined
                a = 1;
            }
    
            test();
    
            function test2() {
                b = 1;
                console.log(b);    // 1,因为执行这句的时候b已经自动升级成了全局变量所以打印1
            }
    
            test2(); 

    例子1:执行test()报错是因为:没有var声明的变量不会发生变量提升!!

            funA;  // undefined
            var funA = function () {
                console.log('输出a1');
            }
    
            funA();  // 输出a1
    
            var funA = function () {
                console.log('输出a2');
            }
    
            funA();  // 输出a2

    例子2主要是变量提升

    var funA;

    var funA = ...

    funA()

    var funA = ...

    funA()

    预编译阶段先初始化得到var funA=undefined,所以第一个funA输出undefined;

    然后顺序执行,先把function(){ console.log('输出a1') }赋值给funA,然后执行funA();

    然后顺序执行,再用function(){ console.log('输出a2') }替换当前funA的值,然后再执行。

            funA();  // 输出a2
            function funA() {
                console.log('输出a1');
            }
    
            funA();  // 输出a2
    
            function funA() {
                console.log('输出a2');
            }
    
            funA();  // 输出a2

    例子3是函数提升

    function funA

    funA()

    funA()

    funA()

    预编译阶段初始化的时候解析到function,后面的funA会替换前面的,因此,这三个函数执行都执行的是后一个funA。

            funA();  // 输出a2
            var funA = function () {
                console.log('输出a1');
            }
    
            funA();  // 输出a1
    
            function funA() {
                console.log('输出a2');
            }
    
            funA();  // 输出a1

    例子4表示函数声明的优先级大于变量声明

    var funA

    function funA

    funA()   【执行的是函数funA】

    var funA = function(){}

    funA() 【执行的是变量赋值后的funA】

    funA()  【同上】

            console.log(number);  // ƒ number() {console.log('test')}
            function number() {
                console.log('test')
            }
            var number = 1;
    
    
            var number2 = 2;
            console.log(number2);  // 2
            function number2() {
                console.log('test')
            }
    
            function number3(x) {
                console.log(x);  // ƒ x() { }
                function x() { }
            }
            number3(5)

    例子5

    第一个demo是演示了函数提升和变量提升,但是由于function number()最先被提升,后面var number的提升会被忽略,所以第一个会输出函数体

    第二个demo是因为预编译结束之后,直接给number2赋值,所以输出的是赋值后的number2

    第三个demo说明函数声明的提升会覆盖函数参数。函数参数其实属于变量的一种形式,它的优先级最高,但是同样会受到函数声明的影响!

    小结一下~

    初始化规则是先处理函数声明,再处理变量声明

    变量提升和函数提升通俗点说就是将变量和函数移动到代码顶,在创建阶段,js解释器会找到需要提升的变量和函数,并给他们在内存中开辟好空间,变量只声明并且赋值undefined,而函数会整个存入内存中!

    在提升过程中,相同函数名的函数会覆盖前面的,函数提升会优先于变量提升

    执行栈:

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

    JavaScript引擎首次读取脚本时,首先将全局执行上下文push到当前执行栈,每当发生函数调用,引擎会给该函数创建一个函数执行上下文并将它push到当前执行栈的栈顶,当栈顶的函数执行完成后,栈顶的函数执行上下文会从执行栈中pop出,交由下一个执行上下文,so程序结束之前,执行栈最底部永远是globalContext

    作用域链(scope chain):

    它在js解释器进入到一个执行环境时初始化完成,并将其分配给当前执行环境。每个执行环境的作用域链由当前环境的VO和父级环境的作用域链构成

    var scope = "global scope";
    function checkscope(){
        var scope = "local scope";
        function f(){
            return scope;
        }
        return f();
    }
    checkscope();
    var scope = "global scope";
    function checkscope(){
        var scope = "local scope";
        function f(){
            return scope;
        }
        return f;
    }
    checkscope()();

    上面这两个例子都输出“local scope”,两者的差别在于:执行栈的变化不一样!两者的流程如下:

    demo1:

    ECStack.push(<checkscope> functionContext);
    ECStack.push(<f> functionContext);
    ECStack.pop();
    ECStack.pop();

    demo2:

    ECStack.push(<checkscope> functionContext);
    ECStack.pop();
    ECStack.push(<f> functionContext);
    ECStack.pop();

    具体的流程分析见JavaScript深入之执行上下文

     

    执行上下文的创建:

    执行上下文分为两个阶段创建:1.创建阶段; 2.执行阶段

    1.创建阶段

    在JavaScript代码执行前,执行上下文处在创建阶段,在创建阶段会确定如下三个事情:

    1. 确定this的值(即This Binding)
    2. 创建词法环境(LexicalEnvironment)
    3. 创建变量环境(VariableEnvironment)

     LexicalEnvironment和VariableEnvironment的区别:前者是存储function声明和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>  
      }  
    }

    只有遇到multiply函数调用时才会创建该函数执行上下文!

    注意:(在声明之前访问变量的区别

    let和cons定义的变量在创建阶段会保持未初始化状态,没有任何和它相关联的值,所以在声明之前访问let和const定义的变量会提示引用错误!

    而var定义的变量会在声明的时候被置为undefined,所以在声明之前访问var定义的变量会输出undefined。

    2.执行阶段

     完成对所有变量的分配,最后执行代码

     

    变量对象(VO)

    每一个执行上下文都会有一个相关联的变量对象,变量对象的属性由在执行上下文中定义的变量(variables)函数声明(function declaration)构成。

    变量对象和当前作用域息息相关,不同作用域的变量对象互不相同!!

    注意!!!函数声明会加到变量对象中,但是函数表达式则不会

    // 函数声明
    function a() {  
        ...
    }
    
    // 这个是函数表达式
    var a = function funA(){  // a会作为变量存在VO中,但是funA不会存在VO中
        ...  
    }

      在全局上下文中:当js编译器开始执行时会初始化一个Global Object,在浏览器端,Global Object == Windows对象 == 全局环境的VO。VO对于程序而言是不可读的,只有编译器才有权访问变量对象,因此Global Object对于程序而言是唯一可读的VO。

      在函数上下文中:参数列表(parameters)也会被加入到变量对象中作为属性。用活动对象(AO)来表示变量对象,活动对象是在进入函数上下文的时刻被创建,这时候对象上的各种属性才能被访问。

    活动对象(activation object

    调用函数时,会创建一个活动对象分配给执行上下文。AO由局部变量arguments初始化而成,所有作为参数传入的值都是该arguments数组的元素。随后,AO被当做VO用于变量初始化。

    以我学习变量对象的例子为例对照记忆:

    function foo(a) {
      var b = 2;
      function c() {}
      var d = function() {};
    
      b = 3;
    }
    
    foo(1);

    初始化时的AO是:

    AO = {
        arguments: {
            0: 1,
            length: 1
        },
        a: 1,
        b: undefined,
        c: reference to function c(){},
        d: undefined
    }

    可以看到形参arguments是直接赋值的,而变量是置为undefined;代码执行后,变量赋值,修改变量的值,此时的AO如下:

    AO = {
        arguments: {
            0: 1,
            length: 1
        },
        a: 1,
        b: 3,
        c: reference to function c(){},
        d: reference to FunctionExpression "d"
    }
  • 相关阅读:
    图解HTTPS
    JQuery 控件
    sql server 中某个字段值合并【转】
    ASP.NET时间函数及其格式转换
    数据库 'tempdb' 的日志已满
    @@ERROR 和 @@ROWCOUNT
    SQL Server中行列转换 Pivot UnPivot 【转】
    Global.asax详解
    SQL Server 2008时提示评估期已过的解决办法
    C# IO读取文件问题:正由另一进程使用
  • 原文地址:https://www.cnblogs.com/ningyn0712/p/11639305.html
Copyright © 2011-2022 走看看