阅读 44

【js基础】执行上下文

当js引擎检测到我们执行到一个程序(全局)或者函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做"执行上下文"。

JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文

当函数执行的时候,才会创建函数执行上下文

image.png

let a = 20;  
const b = 30;  
var c;

function multiply(e, f) {  
 var g = 20;  
 return e * f * g;  
}

c = multiply(20, 30);
复制代码
//全局执行上下文
GlobalExectionContext = {
    // this绑定为全局对象
    ThisBinding: <Global Object>,
    // 词法环境
    LexicalEnvironment: {  
        //环境记录
      EnvironmentRecord: {  
        Type: "Object",  // 对象环境记录
        // 标识符绑定在这里 let const创建的变量a b在这
        a: < uninitialized >,  
        b: < uninitialized >,  
        multiply: < func >  
      }
      // 全局环境外部环境引入为null
      outer: <null>  
    },
  
    VariableEnvironment: {  
      EnvironmentRecord: {  
        Type: "Object",  // 对象环境记录
        // 标识符绑定在这里  var创建的c在这
        c: undefined,  
      }
      // 全局环境外部环境引入为null
      outer: <null>  
    }  
  }

  // 函数执行上下文
  FunctionExectionContext = {
     //由于函数是默认调用 this绑定同样是全局对象
    ThisBinding: <Global Object>,
    // 词法环境
    LexicalEnvironment: {  
      EnvironmentRecord: {  
        Type: "Declarative",  // 声明性环境记录
        // 标识符绑定在这里  arguments对象在这
        Arguments: {0: 20, 1: 30, length: 2},  
      },  
      // 外部环境引入记录为</Global>
      outer: <GlobalEnvironment>  
    },
  
    VariableEnvironment: {  
      EnvironmentRecord: {  
        Type: "Declarative",  // 声明性环境记录
        // 标识符绑定在这里  var创建的g在这
        g: undefined  
      },  
      // 外部环境引入记录为</Global>
      outer: <GlobalEnvironment>  
    }  
  }
复制代码

最后摘抄一段总结吧,感觉总结的很精髓:

1.全局执行上下文一般由浏览器创建,代码执行时就会创建;函数执行上下文只有函数被调用时才会创建,调用多少次函数就会创建多少上下文。

2.调用栈用于存放所有执行上下文,满足FILO规则。

3.执行上下文创建阶段分为绑定this,创建词法环境,变量环境三步,两者区别在于词法环境存放函数声明与const let声明的变量,而变量环境只存储var声明的变量。

4.词法环境主要由环境记录与外部环境引入记录两个部分组成,全局上下文与函数上下文的外部环境引入记录不一样,全局为null,函数为全局环境或者其它函数环境。环境记录也不一样,全局叫对象环境记录,函数叫声明性环境记录。

5.你应该明白了为什么会存在变量提升,函数提升,而let const没有。

6.ES3之前的变量对象与活动对象的概念在ES5之后由词法环境,变量环境来解释,两者概念不冲突,后者理解更为通俗易懂。

7.执行上下文是一个单线程

8.同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待

9.全局上下文只有唯一的一个,它在浏览器关闭时出栈

10.函数的执行上下文的个数没有限制

11.每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

巩固一下执行上下文的理解

function f1(){
    var n=999;
    function f2(){
        alert(n);
    }
    return f2;
}
var result=f1();
result(); // 999
复制代码

因为f1中的函数f2在f1的可执行代码中,并没有被调用执行,因此执行f1时,f2不会创建新的上下文,而直到result执行时,才创建了一个新的。具体演变过程如下。

image.png

看看输出的是什么:


var name = "window";

var p = {
  name: 'Perter',
  getName: function() {

    // 利用变量保存的方式保证其访问的是p对象
    var self = this;
    return function() {
      return self.name;
    }
  }
}

var getName = p.getName();
var _name = getName();
console.log(_name);
复制代码

在很多文章中虽然提到了变量提升,但是具体是怎么回事还真的很多人都说不出来,以后在面试中用变量对象的创建过程跟面试官解释变量提升,保证瞬间提升逼格。

image.png


// 上栗的执行顺序为

// 首先将所有函数声明放入变量对象中
function foo() { console.log('function foo') }

// 其次将所有变量声明放入变量对象中,但是因为foo已经存在同名函数,因此此时会跳过undefined的赋值
// var foo = undefined;

// 然后开始执行阶段代码的执行
console.log(foo); // function foo
foo = 20;
复制代码

执行上下文的练习:


function test() {
    console.log(a);
    console.log(foo());

    var a = 1;
    function foo() {
        return 2;
    }
}

test();
可以直接复制到控制台中输出答案
复制代码

变量对象和活动对象的区别(es3中的概念,es5后变成词法环境)

做一下执行上下文的代码题,检测一下自己
// demo2
function test() {
    console.log(foo);
    console.log(bar);

    var foo = 'Hello';
    console.log(foo);
    var bar = function () {
        return 'world';
    }

    function foo() {
        return 'hello';
    }
}

test();
复制代码

关于这道题,我们要注意一个点:因为var声明的变量当遇到同名的属性时,会跳过而不会覆盖, 所以这里答案是这样的:

image.png

复习的路还很长,加油。。。以下是我摘抄的解答,如果还是不懂的话可以往下看,如果懂了就不用往下看了 image.png

    调用了test方法后,里面就var的字面量声明和函数方法,函数的提升是大于var的,所以,
    foo的函数会被提升到开头,剩下两个var,我们就按原来的顺序看,
    
    function test() {
    // 创建阶段
     function foo() {
        return 'hello';
    }
   // var foo = undefined   根据规范定义,当执行上下文中具有同名的变量声明和函数声明的时候,变量声明的优先级是低于函数声明的,所以函数声明覆盖变量声明,所以这里不覆盖函数同名变量
    var bar = undefined
    console.log(foo); // 打印出函数
    console.log(bar); // undefined
// 赋值执行阶段
    var foo = 'Hello';// 给foo重新赋值后
    console.log(foo);  // 再打印就有值
    var bar = function () {
        return 'world';
    }

   
}

test();
复制代码

JavaScript是这样的,在执行前时期,会把当前执行上下文的代码全部扫描一遍,在这个时期生成一个变量对象VO,当所在执行上下文是函数内部时,这个VO会变成激活对象AO。这个VO/AO包括什么内容呢?

VO/AO包括:变量声明,函数声明,以及函数形参。


function test(a, b) {

var c = 10;

function d() {}

var e = function _e() {};

(function x() {});

}



test(10); // call

对于上面这段代码,进入当前执行上下文生成的变量对象内容是(抽象表示):
AO(test) = {
a: 10,//传参默认值
b: undefined,// 声明的都是undefined初始值
c: undefined,
d: 函数
e: undefined
};
 
复制代码

本文引用于以下链接,如有侵权联系作者可删

github.com/xitu/gold-m…

juejin.cn/post/684490…

github.com/mqyqingfeng…

www.cnblogs.com/echolun/p/1…

segmentfault.com/a/119000001…

github.com/mqyqingfeng…

zhuanlan.zhihu.com/p/72959191

文章分类
前端
文章标签