首页 > 技术文章 > 对javascript执行上下文的理解

moringyaozheng 2020-09-28 17:43 原文

什么是执行上下文?

javascript中代码的运行环境分为一下三种:

1.全局级别的代码,这个是默认代码的运行环境,一旦代码被载入,引擎最先进入的就是这个环境。

2.函数级别的代码,当执行一个函数时,运行函数体中的代码。

3.eval的代码,在eval函数内运行的代码。

为了方便于大家理解,我们可以将执行上下文看做当前代码的运行环境或者作用域。

举个栗子:

在图中一共有4个执行上下文。

紫色的代表全局的上下文。绿色的代表person函数内的上下文。蓝色以及橙色代表person函数内的另外两个函数的上下文。

全局的上下文只有一个,可以被任何其他的上下文所访问到,我们可以在person/firstName/lasrName的上下文中访问到全局上下文中的sayHello变量。

函数上下文的个数是没有任何限制的,每到调用执行一个函数时,引擎就会自动新建出一个函数上下文,也就是局部作用域。我们可以在该局部作用域中声明私有变量等,在外部的上下文中是无法直接访问到该局部作用域内的元素的。

对执行上下文的理解可以归纳总结一下几点:

1.单线程

2.同步执行

3.唯一的一个全局上下文

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

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

执行上下文的建立过程:

现在我们已经知道,每当调用一个函数时,一个新的执行上下文就会被创建出来(局部作用域),在javascript引擎内部,这个上下文的创建过程具体分为两个阶段:

1.建立阶段(发生在当调用一个函数时,但是在执行函数体内的具体代码以前)

 这个阶段做的操作有:

  • 建立变量,函数,arguments对象,参数
  • 建立作用域
  • 确定this的值

2.代码的执行阶段

  • 变量赋值,函数引用,执行其他代码

实际上可以把执行上下文看做一个对象,其中包含了以上3个属性:

executionContextObj = {
      variableObject: {}, //函数中的arguments对象, 参数, 内部的变量以及函数声明 
      scopeChain: {}, //variableObject 以及所有父执行上下文中的variableObject 
      this: {}
}

结合例子详细的分析:

建立阶段具体过程如下:

1.找到当前上下文中的调用函数的代码

2.在执行被调用的函数体中的代码以前,开始创建执行上下文

3.进入第一个阶段-建立阶段:

  • 建立variableObject对象:
  1. 建立arguments对象,检查当前上下文中的参数,建立该对象的属性以及属性值
  2. 检查当前上下文中的函数声明:
    每找到个一个函数声明,就在variableObject下面用函数建立一个属性,属性值就是 指向该函数在内存中的地址的一个引用,如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。
  • 初始化作用域链
  • 确定上下文中的this的指向对象

4.代码执行阶段

执行函数体中的代码,一行一行的运行,给variableObject中的变量属性赋值

下面举个栗子:

function foo (i) {
    var a = 'hello';
    var b = function privateB () {};
    function c () {};
};
foo(22);

在调用foo(22)的时候,建立阶段如下:

executionContextObject = {
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c(),
        a: undefined,
        b: undefined
    },
    scopeChain: {...},
    this: {...}
}

由此可见,在建立阶段,除了arguments,函数的声明,以及参数被赋予了具体的属性值,其他的变量属性默认的都是undefined.。

一旦上述建立阶段结束,引擎就会进入代码的执行阶段,在执行阶段,变量属性会被赋予具体的值

executionContextObject = {
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c(),
        a: 'hello',
        b: pointer to function privateB()
    },
    scopeChain: {...},
    this: {...}
}

局部变量作用域提升的缘由

变量的提升:在函数中声明的变量以及函数,其作用域提升到函数顶部,换句话说就是已进入函数体,就可以访问到其中声明的变量以及函数。

这个的原因就是:在函数被调用的时候,会新创建一个执行上下文,在执行上下文的建立阶段,会先处理arguments.参数,接着是函数声明,最后是变量的声明。

举个栗子:

(function() {
    console.log(typeof foo); // function pointer
    console.log(typeof bar); // undefined

    var foo = 'hello';
    
    var bar = function() {
        return 'world';
    };
    function foo() {
        return 'hello';
    }
})();

 

这里有个要要注意下就是声明的foo函数可以在声明的foo变量之前访问到,

这是因为上下文的建立阶段先是处理arguments.参数,接着是函数声明,最后是变量的声明

 

推荐阅读