关于javascript中的 执行上下文和对象变量

什么是执行上下文

当浏览器的解释器开始执行我们的js代码的时候,js代码运行所处的环境可以被认为是代码的执行上下文,执行上下文(简称-EC)是ECMA-262标准里的一个抽象概念,用于同可执行代码(executable code)概念进行区分。一般来讲,执行上下文可以在以下三种情况产生:

1. 全局上下文(globalContext)   2. function 内部 3. Eval code.  

看个例子,包含全局和function内部上下文

  

紫色框内表示全局的执行上下文,同时内部会有3个不同的Function context, function context可以有多个,但是全局上下文只有一个,并且当解释器开始执行代码的时候就会创建全局上下文并进入。 我们可以创建任意多个Function context,声明一个方法并执行的时候会自动创建该function context,同时创建一块区域,在该区域内创建的变量或其他声明不直接被外部context所访问。

执行上下文堆栈

浏览器内部js解释器是按照单线程的方式实现,意味着内部只能同时在做一件事情,其他的调用都会在被称为执行堆栈的地方排队。看下面的图:

  

当浏览器加载js的时候,就会默认进入全局上下文,如果全局代码中开始执行function,则会创建一个新的execution context,并把该execution context push到栈顶。

如果在function里面又调用内部function,则会执行相同的操作,创建新的execution context,并push到栈顶。看例子:

(function foo(i){
    if(i === 3){
        return;
       }else{
     foo(++i);
    }
}
)(0)

 

  

foo会执行三次,每次执行会生成新的execution context,执行结束则自动出栈。

执行上下文的细节

现在我们知道伴随着function的调用,都会产生一个新的context,在解释器内部,大致分为两个阶段:

Stage I:创建阶段(function被调用,但是在开始执行任何代码之前)

  创建阶段大致做了以下几件事情:

  ①:生成变量对象。该阶段把所有的声明都以key-value的形式提取出来,包括 函数的形参(value为实参的值),function arguments,内部function名(value是对内部function的引用),function内的变量声明(value值统一为undefined)。

  ②:创建作用域链。作用域链包含该上下文中的变量对象和所有父上下文的变量对象,用于变量查找)

  ③:给this赋值。关于this的理解,参考《对javascript this的理解

Stage II:执行阶段(给变量赋值,逐步执行)

理解了以上两个执行阶段后,我们可以大体描绘一下执行上下文中有哪些东西,可以用一个带有3个属性的对象来表示:

executionContextObj = {
    scopeChain: { /* variableObject + all parent execution context's variableObject */ },
    variableObject: { /* function arguments / parameters, inner variable and function declarations */ },
    this: {}
}

对象变量和活动对象 Variable/Activation Object[VO/AO]

executionContextObj在function每次被调用的时候创建,在上文提到的StageI阶段,解释器会对function进行扫描,包括function的arguments, 参数,内部变量声明和内部function声明,扫描的结果会被放到 我们称为 对象变量的对象中(variable object).

看例子:

var a = 10;
 
function test(x) {
  var b = 20;
};
 
test(30);

以上代码对应的变量对象应该为:

VO(global context) = {
    a: 10,
    test: <reference to function>
}

VO(test function context) = {
    x:30,
    b:20        
}

具体分为两种,

全局上下文中的对象变量

首先,我们要给全局对象一个明确的定义:

全局对象(Global object) 是在进入任何执行上下文之前就已经创建了的对象;
这个对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的生命周期终止于程序退出那一刻。

全局对象初始创建阶段将Math、String、Date、parseInt作为自身属性,等属性初始化,同样也可以有额外创建的其它对象作为属性(其可以指向到全局对象自身)。例如,在DOM中,全局对象的window属性就可以引用全局对象自身(当然,并不是所有的具体实现都是这样):

global = {
Math: <...>,
String: <...>
...
...
window: global //引用自身
};

当访问全局对象的属性时通常会忽略掉前缀,这是因为全局对象是不能通过名称直接访问的。不过我们依然可以通过全局上下文的this来访问全局对象,同样也可以递归引用自身。例如,DOM中的window。综上所述,代码可以简写为:

String(10); // 就是global.String(10);

// 带有前缀
window.a = 10; // === global.window.a = 10 === global.a = 10;
this.b = 20; // global.b = 20;

因此,回到全局上下文中的变量对象——在这里,变量对象就是全局对象自己:

VO(globalContext) === global;

非常有必要要理解上述结论,基于这个原理,在全局上下文中声明的对应,我们才可以间接通过全局对象的属性来访问它(例如,事先不知道变量名称)。

var a = new String('test');

alert(a); // 直接访问,在VO(globalContext)里找到:"test"

alert(window['a']); // 间接通过global访问:global === VO(globalContext): "test"
alert(a === this.a); // true

var aKey = 'a';
alert(window[aKey]); // 间接通过动态属性名称访问:"test"

 函数上下文中的变量对象

在函数执行上下文中,VO是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO的角色。

VO(functionContext) === AO;

活动对象是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化。arguments属性的值是Arguments对象:

AO = {
arguments: <ArgO>
};

Arguments对象是活动对象的一个属性,它包括如下属性:

  1. callee — 指向当前函数的引用
  2. length — 真正传递的参数个数
  3. properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。 properties-indexes内部元素的个数等于arguments.length. properties-indexes 的值和实际传递进来的参数之间是共享的。

例如:

function foo(x, y, z) {

// 声明的函数参数数量arguments (x, y, z)
alert(foo.length); // 3

// 真正传进来的参数个数(only x, y)
alert(arguments.length); // 2

// 参数的callee是函数自身
alert(arguments.callee === foo); // true

// 参数共享

alert(x === arguments[0]); // true
alert(x); // 10

arguments[0] = 20;
alert(x); // 20

x = 30;
alert(arguments[0]); // 30

// 不过,没有传进来的参数z,和参数的第3个索引值是不共享的

z = 40;
alert(arguments[2]); // undefined

arguments[2] = 50;
alert(z); // 40

}

foo(10, 20);

 综上,来看一个比较综合的例子:

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

    };
    function c() {

    }
}

foo(22);

foo被调用,StageI阶段:

foo executionContextObj = {
    scopeChain: { ... },
    varaible object :{
        arguments:{
            length:1,
            callee : foo,//对调用foo的引用
            0:22
        }
        i : 22,
        c : <reference function()>,
        a : undefined,
        b : undefined
    },
    this : global
    
}

Stage II :

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            callee:foo
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: 'hello',
        b: pointer to function privateB()
    },
    this: global
}
原文地址:https://www.cnblogs.com/teamobaby/p/3854090.html