01-执行上下文与变量对象

执行上下文

可执行代码
JavaScript中可执行代码(executable code)分为三种:全局代码,函数代码和eval代码。
可执行代码与执行上下文的概念是相对的,在某些语义下,可执行代码与执行上下文是等价的。

执行上下文
执行上下文(Execution Context,EC),也称执行环境,每当控制器执行到可执行代码的时,就会进入到一个执行上下文。
执行上下文可以理解为可执行代码的"运行环境",分为三种:
全局环境:当一段程序开始执行时,会首先进入全局执行文环境,浏览器中是window对象,只有没有关闭浏览器,一直存在。
函数环境:每当函数被调用执行时,就会进入一个新的上下文环境。(函数递归调用也会进入)
eval环境:eval函数调用的时候产生的执行上下文。

执行上下文也可以抽象理解为一个对象,这个对象都有三个属性:
变量对象(variable object)、作用域链(scope chain)、this指针(this value)。

不同执行上下文变量对象略有不同:
全局上下文中的变量对象就是全局对象,允许通过变量对象的属性名来间接访问。
函数上下文中用活动对象来表示变量对象,通过函数的arguments属性初始化。
执行上下文生命周期分为两个阶段:创建阶段和代码执行阶段
创建阶段:创建变量对象,建立作用域链,确定this指向
代码执行阶段:变量赋值,函数引用,执行其他代码

执行上下文栈
JavaScript引擎通过执行上下文栈(Execution Context Stack)来管理执行上下文。
当一个执行上下文(caller)激活了另一个上下文(callee),这个caller就会暂停它自身的执行,将控制权交给callee,于是callee被压入栈顶,称为当前上下文。当这个callee的上下文结束之后被弹出,然后caller从暂停的地方继续执行。一个callee可以用返回(return)或抛出异常(exception)来结束自身的上下文。
ECStack底部永远都是全局上下文(global EC),而顶部就是当前(激活的)执行上下文(active EC)。

    // demo1
    var scope = "global scope";
    function checkscope(){
        var scope = "local scope";
        function foo(){
            return scope;
        }
        return foo();
    }
    checkscope();
    // 1.首先向执行上下文栈中压入全局执上下文
    ECStack = [globalContext];
    // 2.调用checkscope(),checkscope执行上下文被压入ECStack
    ECStack.push[checkscopeContext] = [
        checkscopeContext,
        globalContext
    ];
    // 3.调用foo(),foo执行上下文被压入ECStack
    ECStack.push[fooContext] = [
        fooContext,
        checkscopeContext,
        globalContext
    ];
    // 4.foo()执行结束,foo执行上下文被弹出ECStack
    ECStack.pop[fooContext] = [
        checkscopeContext,
        globalContext
    ];
    // 5.checkscope()执行结束,checkscope执行上下文被弹出ECStack
    ECStack.pop[checkscopeContext] = [globalContext];

    // demo2
    var scope = "global scope";
    function checkscope(){
        var scope = "local scope";
        function foo(){
            return scope;
        }
        return foo;
    }
    checkscope()();

    // 1.首先向执行上下文栈中压入全局执上下文
    ECStack = [globalContext];
    // 2.调用checkscope(),checkscope执行上下文被压入ECStack
    ECStack.push[checkscopeContext] = [
        checkscopeContext,
        globalContext
        ];
    // 3.checkscope()执行结束后返回foo函数体,checkscope执行上下文被弹出ECStack
    ECStack.pop[checkscopeContext] = [globalContext];
    // 4.foo函数体被返回后调用执行,foo执行上下文被压入ECStack
    ECStack.push[fooContext] = [
        fooContext,
        globalContext
    ];

    // 5.foo()执行结束,foo执行上下文被弹出ECStack
    ECStack.pop[fooContext] = [globalContext];

变量对象

变量对象(variable object,VO)是与执行上下文相关的数据作用域,用于存储定义在上下文中的变量和函数声明。
函数表达式不包含在变量对象中。

    var num = 10; // 变量声明
    function fun() {} // 函数声明, FD
    (function bar() {}); // 函数表达式, FE
    console.log(
      this.num === num, // true
      window.fun === fun // true
    );
    console.log(bar); //  "bar" is not defined
    // globalContext.VO = {
        fun: <fun reference>, // 函数fun的引用地址
        num: undefined   // 变量num,赋值为undefined
    }

在全局上下文中,变量对象就是全局对象(在浏览器中是window对象),允许通过全局对象的属性名来访问全局变量。
在函数执行上下文中,用活动对象(active object,AO)来表示变量对象,AO不能直接访问。
活动对象除了变量和函数声明,还存储了形参和arguments对象。
arguments对象是对形参的一个映射,但是值是通过索引来获取(类数组)。

    function foo(x, y) {
      var z = 30;
      function bar() {} // FD
      (function baz() {}); // FE
    }
    foo(10, 20);
    //fooContext.AO = {
        arguments: {
            0: 10,
            1: 20,
            length: 2
            },
        bar: <bar reference>,
        x: 10,
        y: 20,
        z: 30
    }

在进入执行环境时,变量对象会进行如下初始化:
(1)arguments对象,对象中的值被赋予具体的实参值。
(2)函数的形参:创建一个属性,其属性名为形参名,其值为实参的值;对于没有传递的参数,其值为undefined。
(3)函数声明:创建一个属性,其属性名和值都是函数对象创建出来的,其值为指向某个函数对象的引用;如果该函数名的属性已存在,则该属性会被新的引用覆盖。
(4)变量声明:创建一个属性,其属性名即为变量名,其值为undefined。如果变量名与已声明的形参或函数名相同,则会直接跳过,原属性值不会被修改。变量只能使用var关键字声明,不使用var关键字的赋值语句仅仅是给全局对象创建了一个新属性,不是变量。
总结:在进入执行上下文的时候(比如进入全局环境或者某个函数被调用),变量对象除了arguments、函数的声明以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。
在执行到函数内部的具体某个语句的时候,上面所述的值为undefined的变量,其值都会被赋予具体的值。

    function test() {
        var a = 1;
        function foo() {
            return 2;
        }
        var bar = function () {
            return 'hello';
        };
    }
    test();
    // test()调用时进入该函数执行上下文
    testContext = [
        VO: {}, // 创建变量对象
        Scope: {}, // 建立作用域链
        this:{} // 确定this指向
    ]
    // 创建变量对象VO,里面的属性不能被访问
    AO = {
        arguments: {length: 0}, // 初始化Arguments对象
        foo: <foo reference>, // 函数foo的引用地址
        a: undefined   // 变量a,赋值为undefined
        bar: undefined   // 变量bar,值为undefined
    }
    // 执行阶段
    AO = {
        arguments: {length: 0},
        foo: <foo reference>,
        a: 1   // 变量a,赋值该为1
        bar: <bar reference> // 匿名函数的引用地址赋值给bar
    }

每进入到一个执行环境都会创建一个变量对象,这个对象中记录了在当前执行环境中可以访问到的变量和函数,它们以变量对象的属性形式存在。也就是说这个变量对象成为“作用域”这个抽象概念的实体。

参考资料:
《JavaScript高级程序设计》
《JavaScript 标准参考教程》
汤姆大叔-深入理解JavaScript系列

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容