zoukankan      html  css  js  c++  java
  • 关于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
    }
  • 相关阅读:
    js设计模式——代理模式
    js设计模式——策略模式
    js设计模式——单例模式
    Krpano vtourskin.xml 默认皮肤详解
    通过JS动态切换大场景xml
    krpano 户型地图雷达
    微信小程序开发
    CSS3的calc()使用
    Yslow
    微信分享
  • 原文地址:https://www.cnblogs.com/teamobaby/p/3854090.html
Copyright © 2011-2022 走看看