JavaScript中執(zhí)行環(huán)境和棧

在這篇文章中甚侣,我會(huì)深入理解JavaScript最根本的組成之一 : "執(zhí)行環(huán)境(執(zhí)行上下文)"创千。文章結(jié)束后木柬,你應(yīng)該對(duì)解釋器試圖做什么皆串,為什么一些函數(shù)/變量在未聲明時(shí)就可以調(diào)用并且他們的值是如何確定的有一個(gè)清晰的認(rèn)識(shí)。

什么是執(zhí)行環(huán)境(執(zhí)行上下文)
當(dāng)代碼在JavaScript中運(yùn)行的時(shí)候眉枕,代碼在環(huán)境中被執(zhí)行是非常重要的恶复,它會(huì)被評(píng)估為以下之一類型來運(yùn)行:
全局代碼:默認(rèn)環(huán)境,你的代碼第一時(shí)間在這兒運(yùn)行齐遵。
函數(shù)代碼:當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)體的時(shí)候寂玲。
Eval代碼:在eval()函數(shù)中的文本。

你可以在網(wǎng)上查找關(guān)于作用域的大量資料梗摇,這篇文章的目的就是讓事情變得更容易理解拓哟。讓我們把執(zhí)行環(huán)境作為環(huán)境/作用域,當(dāng)前代碼被評(píng)估在這個(gè)環(huán)境/作用域中。現(xiàn)在伶授,讓我們來看一個(gè)例子断序,代碼被評(píng)估某個(gè)類型,這個(gè)例子中類型包括全局和函數(shù)環(huán)境:

img1.png

這里并沒有什么特別的糜烹,我們有一個(gè)全局環(huán)境违诗,全局環(huán)境由紫色邊框表示,還有三個(gè)不同的函數(shù)環(huán)境分別由綠色邊框疮蹦,藍(lán)色邊框和橙色邊框表示诸迟。這里只能由一個(gè)全局環(huán)境,在你的程序中愕乎,全局環(huán)境可以被其他環(huán)境訪問阵苇。

你可以由很多的函數(shù)環(huán)境,每個(gè)函數(shù)都會(huì)創(chuàng)建一個(gè)新的函數(shù)環(huán)境感论,在新的函數(shù)環(huán)境中绅项,會(huì)創(chuàng)建一個(gè)私有作用域,在這個(gè)函數(shù)中創(chuàng)建的任何聲明都不能被當(dāng)前函數(shù)作用域之外的地方訪問比肄。在上面例子中快耿,一個(gè)函數(shù)可以訪問當(dāng)前環(huán)境外部定義的變量囊陡,但是在外部卻無法訪問函數(shù)內(nèi)部聲明的變量。為什么這樣掀亥?這段代碼究竟是如何評(píng)估的撞反?

執(zhí)行環(huán)境棧

JavaScript解釋器在瀏覽器中是單線程的,這意味著瀏覽器在同一時(shí)間內(nèi)只執(zhí)行一個(gè)事件搪花,對(duì)于其他的事件我們把它們排隊(duì)在一個(gè)稱為 執(zhí)行棧的地方痢畜。下表是一個(gè)單線程棧的抽象視圖。

ecstack.jpg

我們已經(jīng)知道鳍侣,當(dāng)瀏覽器第一次加載你的script丁稀,它默認(rèn)的進(jìn)了全局執(zhí)行環(huán)境。如果在你的全局代碼中你調(diào)用了一個(gè)函數(shù)倚聚,那么順序流就會(huì)進(jìn)入到你調(diào)用的函數(shù)當(dāng)中线衫,創(chuàng)建一個(gè)新的執(zhí)行環(huán)境并且把這個(gè)環(huán)境添加到執(zhí)行棧的頂部。

如果你在當(dāng)前的函數(shù)中調(diào)用了其他函數(shù)惑折,同樣的事會(huì)再次發(fā)生授账。執(zhí)行流進(jìn)入內(nèi)部函數(shù),并且創(chuàng)建一個(gè)新的執(zhí)行環(huán)境惨驶,把它添加到已經(jīng)存在的執(zhí)行棧的頂部白热。瀏覽器始終執(zhí)行當(dāng)前在棧頂部的執(zhí)行環(huán)境。一旦函數(shù)完成了當(dāng)前的執(zhí)行環(huán)境粗卜,它就會(huì)被彈出棧的頂部, 把控制權(quán)返回給當(dāng)前執(zhí)行環(huán)境的下個(gè)執(zhí)行環(huán)境屋确。下面例子展示了一個(gè)遞歸函數(shù)和該程序的執(zhí)行棧:

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

這段代碼簡單地調(diào)用了自己三次,由1遞增i的值。每次函數(shù)foo被調(diào)用,一個(gè)新的執(zhí)行環(huán)境就會(huì)被調(diào)用幔嫂。一旦一個(gè)環(huán)境完成了執(zhí)行,它就會(huì)被彈出執(zhí)行棧并且把控制權(quán)返回給當(dāng)前執(zhí)行環(huán)境的下個(gè)執(zhí)行環(huán)境直到再次到達(dá)全局執(zhí)行環(huán)境刨啸。

記住執(zhí)行棧,這兒有五個(gè)關(guān)鍵點(diǎn)

  1. 單線程
  2. 同步執(zhí)行
  3. 一個(gè)全局環(huán)境
  4. 無限的函數(shù)環(huán)境
  5. 函數(shù)被調(diào)用就會(huì)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境识脆,甚至調(diào)用自己设联。

執(zhí)行環(huán)境的詳情

現(xiàn)在我們知道,一個(gè)函數(shù)被調(diào)用就會(huì)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境灼捂。然而解釋器的內(nèi)部离例,每次調(diào)用執(zhí)行環(huán)境會(huì)有兩個(gè)階段:

  1. 創(chuàng)建階段
  • 當(dāng)函數(shù)被調(diào)用,但是為執(zhí)行內(nèi)部代碼之前:
  • 創(chuàng)建一個(gè)作用域鏈纵东。
  • 創(chuàng)建變量粘招,函數(shù)和參數(shù)啥寇。
  • 確定this的值偎球。
  1. 激活/代碼執(zhí)行階段
  • 賦值洒扎,引用函數(shù),解釋/執(zhí)行代碼衰絮。

這可能意味著每個(gè)執(zhí)行環(huán)境在概念上作為一個(gè)對(duì)象并帶有三個(gè)屬性

executionContextObj = {
scopeChain: { /* variableObject + all parent execution context's variableObject */ },
//作用域鏈:{變量對(duì)象+所有父執(zhí)行環(huán)境的變量對(duì)象}
variableObject: { /* function arguments / parameters, inner variable and function declarations */ },
//變量對(duì)象:{函數(shù)形參+內(nèi)部的變量+函數(shù)聲明(但不包含表達(dá)式)}
this: {}
}

活動(dòng)/變量 對(duì)象(AO/VO)

當(dāng)函數(shù)被調(diào)用袍冷,executionContextObj就被創(chuàng)建,該對(duì)象在實(shí)際函數(shù)執(zhí)行前就已創(chuàng)建猫牡。這就是已知的第一個(gè)階段創(chuàng)建階段.在第一階段胡诗,解釋器創(chuàng)建了executionContextObj對(duì)象,通過掃描函數(shù)淌友,傳遞形參煌恢,函數(shù)聲明和局部變量聲明。掃描的結(jié)果成為了變量對(duì)象在executionContextObj中震庭。

  • 這有一個(gè)解釋器是如何評(píng)估代碼的偽概述:
  1. 找到一些代碼來調(diào)用函數(shù)
  2. 在執(zhí)行函數(shù)代碼前瑰抵,創(chuàng)建執(zhí)行環(huán)境
  3. 進(jìn)入創(chuàng)建階段:
  • 初始化作用域鏈
  • 創(chuàng)建變量對(duì)象:
  • 創(chuàng)建arguments對(duì)象,檢查環(huán)境中的參數(shù)器联,初始化名和值二汛,創(chuàng)建一個(gè)參考副本
  • 掃描環(huán)境中內(nèi)的函數(shù)聲明:
  • 某個(gè)函數(shù)被發(fā)現(xiàn),在變量對(duì)象創(chuàng)建一個(gè)屬性拨拓,它是函數(shù)的確切名肴颊。它是一個(gè)指針在內(nèi)存中,指向這個(gè)函數(shù)渣磷。
  • 如果這個(gè)函數(shù)名已存在婿着,這個(gè)指針的值將會(huì)重寫。
  • 掃描環(huán)境內(nèi)的變量聲明
  • 某個(gè)變量聲明被發(fā)現(xiàn)醋界,在變量對(duì)象中創(chuàng)建一個(gè)屬性祟身,他是變量的名,初始化它的值為undefined物独。
  • 如果變量名在變量對(duì)象中已存在袜硫,什么也不做,繼續(xù)掃描挡篓。
  • 在環(huán)境中確定this的值婉陷。
  1. 激活/代碼執(zhí)行階段:在當(dāng)前上下文上運(yùn)行/解釋函數(shù)代碼,并隨著代碼一行行執(zhí)行指派變量的值

看下面例子:

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

};
function c() {

}
}

foo(22);

On calling foo(22), the creation stage looks as follows:
在調(diào)用foo(22)時(shí)官研,創(chuàng)建階段像下面這樣:

fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}

正如你看到的秽澳,創(chuàng)建階段處理了定義屬性的名,但是并不把值賦給變量戏羽,不包括形參和實(shí)參担神。一旦創(chuàng)建階段完成,執(zhí)行流進(jìn)入函數(shù)并且激活/代碼執(zhí)行階段,在函數(shù)執(zhí)行結(jié)束之后,看起來像這樣:

fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
this: { ... }
}

進(jìn)階一言

你可以在網(wǎng)上找到大量的術(shù)語來描述JavaScript進(jìn)階始花。解釋變量和函數(shù)聲明被提升到它們函數(shù)作用域的頂端妄讯。然而孩锡,沒有一個(gè)詳細(xì)的解釋為什么這樣, 現(xiàn)在你配備了關(guān)于解釋器怎么創(chuàng)建活動(dòng)對(duì)象的新知識(shí)亥贸,這會(huì)很明白這是為什么躬窜。看看下面例子:

?(function() {

console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined

var foo = 'hello',
bar = function() {
return 'world';
};

function foo() {
return 'hello';
}

}());?

現(xiàn)在我們能解答的問題有:

為什么在聲明foo之前我們就可以調(diào)用?
如果我們按照創(chuàng)建階段進(jìn)行炕置,我們知道變量在激活/執(zhí)行階段之前已經(jīng)被創(chuàng)建了荣挨。因此,在函數(shù)流開始執(zhí)行朴摊,foo已經(jīng)在活動(dòng)對(duì)象中被定義了默垄。
foo被聲明了兩次, 為什么foo展現(xiàn)出來的是functiton,而不是undefined或者string
我們從創(chuàng)建階段知道,盡管foo被聲明了兩次甚纲,函數(shù)在活動(dòng)對(duì)象中是在變量之前被創(chuàng)建的厕倍,并且如果屬性名在活動(dòng)對(duì)象已經(jīng)存在,我們會(huì)簡單地繞過這個(gè)聲明。

所以贩疙,引用函數(shù)foo()是在活動(dòng)對(duì)象上第一次被創(chuàng)建的讹弯, 當(dāng)我們解釋到 var foo的時(shí)候,我們發(fā)現(xiàn)屬性名foo已經(jīng)存在这溅,所以代碼不會(huì)做任何處理组民,只是繼續(xù)進(jìn)行

為什么bar是undefined?

bar確實(shí)是一個(gè)變量悲靴,并且值是一個(gè)函數(shù)臭胜。我們知道變量是在創(chuàng)建階段被創(chuàng)建的,但是它們的值被初始化為undefined癞尚。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耸三,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子浇揩,更是在濱河造成了極大的恐慌仪壮,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胳徽,死亡現(xiàn)場離奇詭異积锅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)养盗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門缚陷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人往核,你說我怎么就攤上這事箫爷。” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵虎锚,是天一觀的道長硫痰。 經(jīng)常有香客問我,道長翁都,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任谅猾,我火速辦了婚禮柄慰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘税娜。我一直安慰自己坐搔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布敬矩。 她就那樣靜靜地躺著概行,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弧岳。 梳的紋絲不亂的頭發(fā)上凳忙,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音禽炬,去河邊找鬼涧卵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛腹尖,可吹牛的內(nèi)容都是我干的柳恐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼热幔,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼乐设!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绎巨,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤近尚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后场勤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肿男,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年却嗡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舶沛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窗价,死狀恐怖如庭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤坪它,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布骤竹,位于F島的核電站,受9級(jí)特大地震影響往毡,放射性物質(zhì)發(fā)生泄漏蒙揣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一开瞭、第九天 我趴在偏房一處隱蔽的房頂上張望懒震。 院中可真熱鬧,春花似錦嗤详、人聲如沸个扰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽递宅。三九已至,卻和暖如春苍狰,著一層夾襖步出監(jiān)牢的瞬間办龄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工淋昭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留土榴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓响牛,卻偏偏與公主長得像玷禽,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子呀打,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line)矢赁,也就是一...
    悟名先生閱讀 4,118評(píng)論 0 13
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,089評(píng)論 1 32
  • 原文地址:C語言函數(shù)調(diào)用棧(一)C語言函數(shù)調(diào)用棧(二) 0 引言 程序的執(zhí)行過程可看作連續(xù)的函數(shù)調(diào)用。當(dāng)一個(gè)函數(shù)執(zhí)...
    小豬啊嗚閱讀 4,590評(píng)論 1 19
  • 金星采訪楊冪:如果你想給你爸媽買一套房子,你會(huì)跟劉愷威商量嗎昼榛? 楊冪說:不會(huì)的境肾,因?yàn)槲屹I得起。 很多人說女人沒有必...
    龐黑豆閱讀 313評(píng)論 0 0
  • 親子日記第九十二 星期一 晴 孩子有優(yōu)點(diǎn),但是缺點(diǎn)太多了奥喻。今天早上偶宫,起床時(shí)間到了,孩子已經(jīng)睡醒了...
    葉落悠悠閱讀 139評(píng)論 0 0