【你不知道的JavaScript】(三)執(zhí)行上下文及其生命周期

一案怯、執(zhí)行上下文概念

JavaScript代碼的執(zhí)行過(guò)程分為兩個(gè)階段:

  • 代碼編譯階段:由編譯器完成卜录,將代碼翻譯成可執(zhí)行代碼
  • 代碼執(zhí)行階段:由引擎完成少办,主要任務(wù)是執(zhí)行可執(zhí)行代碼

其中可執(zhí)行代碼分為三種:全局代碼扼鞋、函數(shù)代碼获讳、eval代碼

有關(guān)JavaScript代碼的執(zhí)行過(guò)程可查看《【你不知道的JavaScript】(一)作用域與詞法作用域》一文阴颖。

簡(jiǎn)單來(lái)說(shuō),當(dāng)在代碼執(zhí)行階段執(zhí)行到一個(gè)函數(shù)的時(shí)候丐膝,就會(huì)進(jìn)行準(zhǔn)備工作量愧,這里的“準(zhǔn)備工作”,就叫做"執(zhí)行上下文(EC)"帅矗,也叫執(zhí)行上下文環(huán)境偎肃,也叫執(zhí)行環(huán)境。

當(dāng)JavaScript代碼執(zhí)行時(shí)浑此,會(huì)進(jìn)入不同的執(zhí)行上下文累颂,而每個(gè)執(zhí)行上下文的組成,基本如下:

當(dāng)前執(zhí)行上下文(EC)

二、執(zhí)行上下文生命周期

當(dāng)調(diào)用一個(gè)函數(shù)時(shí)(激活)紊馏,一個(gè)新的執(zhí)行上下文就會(huì)被創(chuàng)建料饥。而一個(gè)執(zhí)行上下文的生命周期可以分為兩個(gè)階段:

  • 創(chuàng)建階段:在這個(gè)階段中,執(zhí)行上下文會(huì)分別創(chuàng)建變量對(duì)象朱监,建立作用域鏈岸啡,以及確定this的指向。
  • 執(zhí)行階段:創(chuàng)建完成之后赫编,就會(huì)開(kāi)始執(zhí)行代碼巡蘸,這個(gè)時(shí)候,會(huì)完成變量賦值沛慢,函數(shù)引用赡若,以及執(zhí)行其他代碼。
執(zhí)行上下文生命周期

詳細(xì)了解執(zhí)行上下文極為重要团甲,因?yàn)槠渲猩婕暗搅俗兞繉?duì)象逾冬,作用域鏈,this等極為重要的概念躺苦,它關(guān)系到我們能不能真正理解JavaScript身腻,下面我們分別了解幾個(gè)概念。

(一)變量對(duì)象

1. 變量對(duì)象的創(chuàng)建過(guò)程

(1) 建立arguments對(duì)象匹厘。檢查當(dāng)前上下文中的參數(shù)嘀趟,建立該對(duì)象下的屬性與屬性值。

(2) 檢查當(dāng)前上下文的函數(shù)聲明愈诚,也就是使用function關(guān)鍵字聲明的函數(shù)她按。在變量對(duì)象中以函數(shù)名建立一個(gè)屬性,屬性值為指向該函數(shù)所在內(nèi)存地址的引用炕柔。如果函數(shù)名的屬性已經(jīng)存在酌泰,那么該屬性將會(huì)被新的引用所覆蓋。

(3) 檢查當(dāng)前上下文中的變量聲明匕累,每找到一個(gè)變量聲明陵刹,就在變量對(duì)象中以變量名建立一個(gè)屬性,屬性值為undefined欢嘿。如果該變量名的屬性已經(jīng)存在衰琐,為了防止同名的函數(shù)被修改為undefined,則會(huì)直接跳過(guò)炼蹦,原屬性值不會(huì)被修改羡宙。

變量對(duì)象創(chuàng)建過(guò)程
function foo() { console.log('function foo') }
var foo = 20;

console.log(foo); // 20

// ↑以上代碼中,變量聲明的 foo 遇到函數(shù)聲明的 foo 會(huì)跳過(guò)框弛,
// 可是為什么最后 foo 的輸出結(jié)果仍然是被覆蓋了呢辛辨?
// 那是因?yàn)槿龡l規(guī)則僅僅適用于變量對(duì)象的創(chuàng)建過(guò)程,也就是執(zhí)行上下文的創(chuàng)建過(guò)程。
// 而 foo=20 是在執(zhí)行上下文的執(zhí)行過(guò)程中運(yùn)行的斗搞,輸出結(jié)果自然會(huì)是20指攒。

再來(lái)看另外一個(gè)例子:

console.log(foo); // ? foo() { console.log('function foo') }
function foo() { console.log('function foo') }
var foo = 20;

// 上栗的執(zhí)行順序?yàn)?// 首先將所有函數(shù)聲明放入變量對(duì)象中
function foo() { console.log('function foo') }

// 其次將所有變量聲明放入變量對(duì)象中,
// 但是因?yàn)閒oo已經(jīng)存在同名函數(shù)僻焚,因此此時(shí)會(huì)跳過(guò)undefined的賦值
// var foo = undefined;

// 然后開(kāi)始執(zhí)行階段代碼的執(zhí)行
console.log(foo); // function foo
foo = 20;

2. 變量對(duì)象與活動(dòng)對(duì)象

變量對(duì)象與活動(dòng)對(duì)象其實(shí)都是同一個(gè)對(duì)象允悦,只是處于執(zhí)行上下文的不同生命周期。不過(guò)只有處于函數(shù)調(diào)用棧棧頂?shù)膱?zhí)行上下文中的變量對(duì)象虑啤,才會(huì)變成活動(dòng)對(duì)象隙弛。

function test() {
    console.log(a);
    console.log(foo());

    var a = 1;
    function foo() {
        return 2;
    }
}

test();

↑以上代碼中,全局作用域中運(yùn)行test()時(shí)狞山,test()的執(zhí)行上下文開(kāi)始創(chuàng)建全闷。為了便于理解,我們用如下的形式來(lái)表示:

// 創(chuàng)建過(guò)程
testEC = {
    // VO 為 Variable Object的縮寫(xiě)萍启,即變量對(duì)象
    VO: {
        //注:在瀏覽器的展示中总珠,函數(shù)的參數(shù)可能并不是放在arguments對(duì)象中,
        //這里為了方便理解勘纯,我做了這樣的處理
        arguments: {...},  
        foo: <foo reference>,  // 表示 foo 的地址引用
        a: undefined,
        this: Window
    },
    scopeChain: {}
}

未進(jìn)入執(zhí)行階段之前局服,變量對(duì)象中的屬性都不能訪問(wèn)!但是進(jìn)入執(zhí)行階段之后驳遵,變量對(duì)象轉(zhuǎn)變?yōu)榱嘶顒?dòng)對(duì)象淫奔,里面的屬性都能被訪問(wèn)了,然后開(kāi)始進(jìn)行執(zhí)行階段的操作堤结。

// 執(zhí)行階段
VO ->  AO   // Active Object
AO = {
    arguments: {...},
    foo: <foo reference>,
    a: 1,
    this: Window
}

因此唆迁,上面例子的執(zhí)行順序如下:

function test() {
    function foo() {
        return 2;
    }
    var a;
    console.log(a); // undefined
    console.log(foo()); // 2
    a = 1;
}

test();

3. 全局上下文的變量對(duì)象

全局上下文有一個(gè)特殊的地方,它的變量對(duì)象竞穷,就是window對(duì)象媒惕。而這個(gè)特殊,在this指向上也同樣適用来庭,this也是指向window

除此之外穿挨,全局上下文的生命周期月弛,與程序的生命周期一致,只要程序運(yùn)行不結(jié)束科盛,比如關(guān)掉瀏覽器窗口帽衙,全局上下文就會(huì)一直存在。其他所有的上下文環(huán)境贞绵,都能直接訪問(wèn)全局上下文的屬性厉萝。

(二)作用域鏈

作用域鏈本質(zhì)上是一個(gè)指向當(dāng)前環(huán)境與上層環(huán)境的一系列變量對(duì)象的指針列表(它只引用但不實(shí)際包含變量對(duì)象),作用域鏈保證了當(dāng)前執(zhí)行環(huán)境對(duì)符合訪問(wèn)權(quán)限的變量和函數(shù)的有序訪問(wèn)。

var a = 1;             
function out() {
    var b = 2;
    function inner() {
        var c = 3;
        console.log(a+b+c);
    }
    inner();          
}
out();

首先谴垫,代碼開(kāi)始運(yùn)行時(shí)就創(chuàng)建了全局上下文環(huán)境章母,接著運(yùn)行到out()時(shí)創(chuàng)建 out函數(shù)的執(zhí)行上下文,最后運(yùn)行到inner()時(shí)創(chuàng)建 inner函數(shù)的執(zhí)行上下文翩剪,我們?cè)O(shè)定他們的變量對(duì)象分別為VO(global)乳怎,VO(out), VO(inner)

我們可以直接用一個(gè)數(shù)組來(lái)表示作用域鏈前弯,數(shù)組的第一項(xiàng)scopeChain[0]為作用域鏈的最前端蚪缀,而數(shù)組的最后一項(xiàng),為作用域鏈的最末端恕出,所有的最末端都為全局變量對(duì)象询枚。

  1. 全局的作用域鏈:由于它只含全局作用域,沒(méi)有上級(jí)浙巫,因此它的作用域鏈只指向本身的全局變量對(duì)象金蜀。查找標(biāo)識(shí)符時(shí)只能從本身的全局變量對(duì)象中查找。
// 全局上下文環(huán)境
globalEC = {
    VO: {
        out: <out reference>,  // 表示 out 的地址引用
        a: undefined
    },
    scopeChain: [VO(global)], // 作用域鏈
}
  1. 函數(shù)out的作用域鏈:可以引用函數(shù)out本身的變量對(duì)象以及全局的變量對(duì)象狈醉。查找標(biāo)識(shí)符時(shí)廉油,先在函數(shù)out變量對(duì)象中尋找,找不到的話再去上一級(jí)全局變量對(duì)象查找苗傅。
// out 函數(shù)的執(zhí)行上下文
outEC = {
    VO: {
        arguments: {...},
        inner: <inner reference>,  // 表示 inner 的地址引用
        b: undefined
    },
    scopeChain: [VO(out), VO(global)], // 作用域鏈
}
函數(shù)out作用域鏈
  1. 函數(shù)inner的作用域鏈:可以引用函數(shù)inner本身的變量對(duì)象和上一級(jí)out函數(shù)的變量對(duì)象以及全局的變量對(duì)象抒线。查找標(biāo)識(shí)符時(shí)依次從innerout渣慕,全局變量對(duì)象中查找嘶炭。
innerEC = {
    VO: {
        arguments: {...},  
        c: undefined,
    },  // 變量對(duì)象
    scopeChain: [VO(inner), VO(out), VO(global)], // 作用域鏈
}
函數(shù)inner作用域鏈

(三)this指向

有關(guān)this的指向的詳情,可查看《【你不知道的JavaScript】(四)this的全面解析》逊桦。

三眨猎、執(zhí)行上下文棧

執(zhí)行上下文可以理解為當(dāng)前代碼的執(zhí)行環(huán)境,JavaScript中的運(yùn)行環(huán)境大概包括三種情況:

  • 全局環(huán)境JavaScript代碼運(yùn)行起來(lái)會(huì)首先進(jìn)入該環(huán)境
  • 函數(shù)環(huán)境:當(dāng)函數(shù)被調(diào)用執(zhí)行時(shí)强经,會(huì)進(jìn)入當(dāng)前函數(shù)中執(zhí)行代碼
  • eval

在代碼開(kāi)始執(zhí)行時(shí)睡陪,首先會(huì)產(chǎn)生一個(gè)全局執(zhí)行上下文環(huán)境,調(diào)用函數(shù)時(shí)匿情,會(huì)產(chǎn)生函數(shù)執(zhí)行上下文環(huán)境兰迫,函數(shù)調(diào)用完成后,它的執(zhí)行上下文環(huán)境以及其中的數(shù)據(jù)都會(huì)被銷毀炬称,重新回到全局執(zhí)行環(huán)境汁果,網(wǎng)頁(yè)關(guān)閉后全局執(zhí)行環(huán)境也會(huì)銷毀。其實(shí)這是一個(gè)壓棧出棧的過(guò)程玲躯,全局上下文環(huán)境永遠(yuǎn)在棧底据德,而當(dāng)前正在執(zhí)行的函數(shù)上下文在棧頂鳄乏。

var a = 1;             //1.進(jìn)入全局上下文環(huán)境
function out() {
    var b = 2;
    function inner() {
        var c = 3;
        console.log(a+b+c);
    }
    inner();          //3.進(jìn)入inner函數(shù)上下文環(huán)境
}
out(); //2.進(jìn)入out函數(shù)上下文環(huán)境

↑以上代碼的執(zhí)行會(huì)經(jīng)歷以下過(guò)程:

  1. 當(dāng)代碼開(kāi)始執(zhí)行時(shí)就創(chuàng)建全局執(zhí)行上下文環(huán)境,全局上下文入棧棘利。
  2. 全局上下文入棧后橱野,其中的代碼開(kāi)始執(zhí)行,進(jìn)行賦值赡译、函數(shù)調(diào)用等操作仲吏,執(zhí)行到out()時(shí),激活函數(shù)out創(chuàng)建自己的執(zhí)行上下文環(huán)境蝌焚,out函數(shù)上下文入棧裹唆。
  3. out函數(shù)上下文入棧后,其中的代碼開(kāi)始執(zhí)行只洒,進(jìn)行賦值许帐、函數(shù)調(diào)用等操作,執(zhí)行到inner()時(shí)毕谴,激活函數(shù)inner創(chuàng)建自己的執(zhí)行上下文環(huán)境成畦,inner函數(shù)上下文入棧
  4. inner函數(shù)上下文入棧后涝开,其中的代碼開(kāi)始執(zhí)行循帐,進(jìn)行賦值、函數(shù)調(diào)用舀武、打印等操作拄养,由于里面沒(méi)有可以生成其他執(zhí)行上下文的需要,所有代碼執(zhí)行完畢后银舱,inner函數(shù)上下文出棧瘪匿。
  5. inner函數(shù)上下文出棧,又回到了out函數(shù)執(zhí)行上下文環(huán)境寻馏,接著執(zhí)行out函數(shù)中后面剩下的代碼棋弥,由于后面沒(méi)有可以生成其他執(zhí)行上下文的需要,所有代碼執(zhí)行完畢后诚欠,out函數(shù)上下文出棧顽染。
  6. out函數(shù)上下文出棧后,又回到了全局執(zhí)行上下文環(huán)境轰绵,直到瀏覽器窗口關(guān)閉家乘,全局上下文出棧
執(zhí)行上下文入棧出棧的全過(guò)程

我們可以得到一些結(jié)論:

  • 全局上下文在代碼開(kāi)始執(zhí)行時(shí)就創(chuàng)建藏澳,只有唯一的一個(gè),永遠(yuǎn)在棧底耀找,瀏覽器窗口關(guān)閉時(shí)出棧翔悠。
  • 函數(shù)被調(diào)用的時(shí)候創(chuàng)建上下文環(huán)境业崖。
  • 只有棧頂?shù)纳舷挛奶幱诨顒?dòng)狀態(tài),執(zhí)行其中的代碼蓄愁。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末双炕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子撮抓,更是在濱河造成了極大的恐慌妇斤,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丹拯,死亡現(xiàn)場(chǎng)離奇詭異站超,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)乖酬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)死相,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人咬像,你說(shuō)我怎么就攤上這事算撮。” “怎么了县昂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵肮柜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我倒彰,道長(zhǎng)审洞,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任狸驳,我火速辦了婚禮预明,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘耙箍。我一直安慰自己撰糠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布辩昆。 她就那樣靜靜地躺著阅酪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汁针。 梳的紋絲不亂的頭發(fā)上术辐,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音施无,去河邊找鬼辉词。 笑死,一個(gè)胖子當(dāng)著我的面吹牛猾骡,可吹牛的內(nèi)容都是我干的瑞躺。 我是一名探鬼主播敷搪,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼幢哨!你這毒婦竟也來(lái)了赡勘?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捞镰,失蹤者是張志新(化名)和其女友劉穎闸与,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體岸售,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡践樱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冰评。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片映胁。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖甲雅,靈堂內(nèi)的尸體忽然破棺而出解孙,到底是詐尸還是另有隱情,我是刑警寧澤抛人,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布弛姜,位于F島的核電站,受9級(jí)特大地震影響妖枚,放射性物質(zhì)發(fā)生泄漏廷臼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一绝页、第九天 我趴在偏房一處隱蔽的房頂上張望荠商。 院中可真熱鬧,春花似錦续誉、人聲如沸莱没。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)饰躲。三九已至,卻和暖如春臼隔,著一層夾襖步出監(jiān)牢的瞬間嘹裂,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工摔握, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寄狼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓氨淌,卻偏偏與公主長(zhǎng)得像例嘱,于是被迫代替她去往敵國(guó)和親狡逢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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