01-執(zhí)行上下文與變量對象

執(zhí)行上下文

可執(zhí)行代碼
JavaScript中可執(zhí)行代碼(executable code)分為三種:全局代碼袭厂,函數(shù)代碼和eval代碼。
可執(zhí)行代碼與執(zhí)行上下文的概念是相對的,在某些語義下拓春,可執(zhí)行代碼與執(zhí)行上下文是等價的。

執(zhí)行上下文
執(zhí)行上下文(Execution Context亚隅,EC)硼莽,也稱執(zhí)行環(huán)境,每當(dāng)控制器執(zhí)行到可執(zhí)行代碼的時煮纵,就會進入到一個執(zhí)行上下文懂鸵。
執(zhí)行上下文可以理解為可執(zhí)行代碼的"運行環(huán)境",分為三種:
全局環(huán)境:當(dāng)一段程序開始執(zhí)行時行疏,會首先進入全局執(zhí)行文環(huán)境匆光,瀏覽器中是window對象,只有沒有關(guān)閉瀏覽器酿联,一直存在殴穴。
函數(shù)環(huán)境:每當(dāng)函數(shù)被調(diào)用執(zhí)行時,就會進入一個新的上下文環(huán)境。(函數(shù)遞歸調(diào)用也會進入)
eval環(huán)境:eval函數(shù)調(diào)用的時候產(chǎn)生的執(zhí)行上下文采幌。

執(zhí)行上下文也可以抽象理解為一個對象劲够,這個對象都有三個屬性:
變量對象(variable object)、作用域鏈(scope chain)休傍、this指針(this value)征绎。

不同執(zhí)行上下文變量對象略有不同:
全局上下文中的變量對象就是全局對象,允許通過變量對象的屬性名來間接訪問磨取。
函數(shù)上下文中用活動對象來表示變量對象人柿,通過函數(shù)的arguments屬性初始化。
執(zhí)行上下文生命周期分為兩個階段:創(chuàng)建階段和代碼執(zhí)行階段
創(chuàng)建階段:創(chuàng)建變量對象忙厌,建立作用域鏈凫岖,確定this指向
代碼執(zhí)行階段:變量賦值,函數(shù)引用逢净,執(zhí)行其他代碼

執(zhí)行上下文棧
JavaScript引擎通過執(zhí)行上下文棧(Execution Context Stack)來管理執(zhí)行上下文哥放。
當(dāng)一個執(zhí)行上下文(caller)激活了另一個上下文(callee),這個caller就會暫停它自身的執(zhí)行爹土,將控制權(quán)交給callee甥雕,于是callee被壓入棧頂,稱為當(dāng)前上下文胀茵。當(dāng)這個callee的上下文結(jié)束之后被彈出社露,然后caller從暫停的地方繼續(xù)執(zhí)行。一個callee可以用返回(return)或拋出異常(exception)來結(jié)束自身的上下文琼娘。
ECStack底部永遠都是全局上下文(global EC)峭弟,而頂部就是當(dāng)前(激活的)執(zhí)行上下文(active EC)。

    // demo1
    var scope = "global scope";
    function checkscope(){
        var scope = "local scope";
        function foo(){
            return scope;
        }
        return foo();
    }
    checkscope();
    // 1.首先向執(zhí)行上下文棧中壓入全局執(zhí)上下文
    ECStack = [globalContext];
    // 2.調(diào)用checkscope()脱拼,checkscope執(zhí)行上下文被壓入ECStack
    ECStack.push[checkscopeContext] = [
        checkscopeContext,
        globalContext
    ];
    // 3.調(diào)用foo()瞒瘸,foo執(zhí)行上下文被壓入ECStack
    ECStack.push[fooContext] = [
        fooContext,
        checkscopeContext,
        globalContext
    ];
    // 4.foo()執(zhí)行結(jié)束,foo執(zhí)行上下文被彈出ECStack
    ECStack.pop[fooContext] = [
        checkscopeContext,
        globalContext
    ];
    // 5.checkscope()執(zhí)行結(jié)束挪拟,checkscope執(zhí)行上下文被彈出ECStack
    ECStack.pop[checkscopeContext] = [globalContext];

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

    // 1.首先向執(zhí)行上下文棧中壓入全局執(zhí)上下文
    ECStack = [globalContext];
    // 2.調(diào)用checkscope()挨务,checkscope執(zhí)行上下文被壓入ECStack
    ECStack.push[checkscopeContext] = [
        checkscopeContext,
        globalContext
        ];
    // 3.checkscope()執(zhí)行結(jié)束后返回foo函數(shù)體,checkscope執(zhí)行上下文被彈出ECStack
    ECStack.pop[checkscopeContext] = [globalContext];
    // 4.foo函數(shù)體被返回后調(diào)用執(zhí)行玉组,foo執(zhí)行上下文被壓入ECStack
    ECStack.push[fooContext] = [
        fooContext,
        globalContext
    ];

    // 5.foo()執(zhí)行結(jié)束谎柄,foo執(zhí)行上下文被彈出ECStack
    ECStack.pop[fooContext] = [globalContext];

變量對象

變量對象(variable object,VO)是與執(zhí)行上下文相關(guān)的數(shù)據(jù)作用域惯雳,用于存儲定義在上下文中的變量和函數(shù)聲明朝巫。
函數(shù)表達式不包含在變量對象中。

    var num = 10; // 變量聲明
    function fun() {} // 函數(shù)聲明, FD
    (function bar() {}); // 函數(shù)表達式, FE
    console.log(
      this.num === num, // true
      window.fun === fun // true
    );
    console.log(bar); //  "bar" is not defined
    // globalContext.VO = {
        fun: <fun reference>, // 函數(shù)fun的引用地址
        num: undefined   // 變量num石景,賦值為undefined
    }

在全局上下文中劈猿,變量對象就是全局對象(在瀏覽器中是window對象)拙吉,允許通過全局對象的屬性名來訪問全局變量。
在函數(shù)執(zhí)行上下文中揪荣,用活動對象(active object筷黔,AO)來表示變量對象,AO不能直接訪問仗颈。
活動對象除了變量和函數(shù)聲明佛舱,還存儲了形參和arguments對象。
arguments對象是對形參的一個映射挨决,但是值是通過索引來獲取(類數(shù)組)请祖。

    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
    }

在進入執(zhí)行環(huán)境時,變量對象會進行如下初始化:
(1)arguments對象脖祈,對象中的值被賦予具體的實參值肆捕。
(2)函數(shù)的形參:創(chuàng)建一個屬性,其屬性名為形參名盖高,其值為實參的值慎陵;對于沒有傳遞的參數(shù),其值為undefined或舞。
(3)函數(shù)聲明:創(chuàng)建一個屬性荆姆,其屬性名和值都是函數(shù)對象創(chuàng)建出來的蒙幻,其值為指向某個函數(shù)對象的引用映凳;如果該函數(shù)名的屬性已存在,則該屬性會被新的引用覆蓋邮破。
(4)變量聲明:創(chuàng)建一個屬性诈豌,其屬性名即為變量名,其值為undefined抒和。如果變量名與已聲明的形參或函數(shù)名相同矫渔,則會直接跳過,原屬性值不會被修改摧莽。變量只能使用var關(guān)鍵字聲明庙洼,不使用var關(guān)鍵字的賦值語句僅僅是給全局對象創(chuàng)建了一個新屬性,不是變量镊辕。
總結(jié):在進入執(zhí)行上下文的時候(比如進入全局環(huán)境或者某個函數(shù)被調(diào)用)油够,變量對象除了arguments、函數(shù)的聲明以及參數(shù)被賦予了具體的屬性值征懈,其它的變量屬性默認的都是undefined石咬。
在執(zhí)行到函數(shù)內(nèi)部的具體某個語句的時候,上面所述的值為undefined的變量卖哎,其值都會被賦予具體的值鬼悠。

    function test() {
        var a = 1;
        function foo() {
            return 2;
        }
        var bar = function () {
            return 'hello';
        };
    }
    test();
    // test()調(diào)用時進入該函數(shù)執(zhí)行上下文
    testContext = [
        VO: {}, // 創(chuàng)建變量對象
        Scope: {}, // 建立作用域鏈
        this:{} // 確定this指向
    ]
    // 創(chuàng)建變量對象VO删性,里面的屬性不能被訪問
    AO = {
        arguments: {length: 0}, // 初始化Arguments對象
        foo: <foo reference>, // 函數(shù)foo的引用地址
        a: undefined   // 變量a,賦值為undefined
        bar: undefined   // 變量bar焕窝,值為undefined
    }
    // 執(zhí)行階段
    AO = {
        arguments: {length: 0},
        foo: <foo reference>,
        a: 1   // 變量a蹬挺,賦值該為1
        bar: <bar reference> // 匿名函數(shù)的引用地址賦值給bar
    }

每進入到一個執(zhí)行環(huán)境都會創(chuàng)建一個變量對象,這個對象中記錄了在當(dāng)前執(zhí)行環(huán)境中可以訪問到的變量和函數(shù)它掂,它們以變量對象的屬性形式存在汗侵。也就是說這個變量對象成為“作用域”這個抽象概念的實體。

參考資料:
《JavaScript高級程序設(shè)計》
《JavaScript 標準參考教程》
湯姆大叔-深入理解JavaScript系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末群发,一起剝皮案震驚了整個濱河市晰韵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熟妓,老刑警劉巖雪猪,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異起愈,居然都是意外死亡只恨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門抬虽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來官觅,“玉大人,你說我怎么就攤上這事阐污⌒莸樱” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵笛辟,是天一觀的道長功氨。 經(jīng)常有香客問我,道長手幢,這世上最難降的妖魔是什么捷凄? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮围来,結(jié)果婚禮上跺涤,老公的妹妹穿的比我還像新娘。我一直安慰自己监透,他們只是感情好桶错,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著才漆,像睡著了一般牛曹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上醇滥,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天黎比,我揣著相機與錄音超营,去河邊找鬼。 笑死阅虫,一個胖子當(dāng)著我的面吹牛演闭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播颓帝,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼米碰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了购城?” 一聲冷哼從身側(cè)響起吕座,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘪板,沒想到半個月后吴趴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡侮攀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年锣枝,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兰英。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡撇叁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出畦贸,到底是詐尸還是另有隱情陨闹,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布家制,位于F島的核電站正林,受9級特大地震影響泡一,放射性物質(zhì)發(fā)生泄漏颤殴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一鼻忠、第九天 我趴在偏房一處隱蔽的房頂上張望涵但。 院中可真熱鬧,春花似錦帖蔓、人聲如沸矮瘟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澈侠。三九已至,卻和暖如春埋酬,著一層夾襖步出監(jiān)牢的瞬間哨啃,已是汗流浹背烧栋。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拳球,地道東北人审姓。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像祝峻,于是被迫代替她去往敵國和親魔吐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容