JS引擎線程的執(zhí)行過(guò)程的三個(gè)階段(一)

瀏覽器首先按順序加載由<script>標(biāo)簽分割的js代碼塊僵驰,加載js代碼塊完畢后想际,立刻進(jìn)入以下三個(gè)階段咽瓷,然后再按順序查找下一個(gè)代碼塊崔挖,再繼續(xù)執(zhí)行以下三個(gè)階段贸街,無(wú)論是外部腳本文件(不異步加載)還是內(nèi)部腳本代碼塊,都是一樣的原理狸相,并且都在同一個(gè)全局作用域中薛匪。

JS引擎線程的執(zhí)行過(guò)程的三個(gè)階段:

  • 語(yǔ)法分析
  • 預(yù)編譯階段
  • 執(zhí)行階段

一. 語(yǔ)法分析

分析該js腳本代碼塊的語(yǔ)法是否正確,如果出現(xiàn)不正確脓鹃,則向外拋出一個(gè)語(yǔ)法錯(cuò)誤(SyntaxError)逸尖,停止該js代碼塊的執(zhí)行,然后繼續(xù)查找并加載下一個(gè)代碼塊瘸右;如果語(yǔ)法正確娇跟,則進(jìn)入預(yù)編譯階段。

下面階段的代碼執(zhí)行不會(huì)再進(jìn)行語(yǔ)法校驗(yàn)太颤,語(yǔ)法分析在代碼塊加載完畢時(shí)統(tǒng)一檢驗(yàn)語(yǔ)法苞俘。

二. 預(yù)編譯階段

1. js的運(yùn)行環(huán)境

  • 全局環(huán)境(JS代碼加載完畢后,進(jìn)入代碼預(yù)編譯即進(jìn)入全局環(huán)境)

  • 函數(shù)環(huán)境(函數(shù)調(diào)用執(zhí)行時(shí)龄章,進(jìn)入該函數(shù)環(huán)境吃谣,不同的函數(shù)則函數(shù)環(huán)境不同)

  • eval(不建議使用乞封,會(huì)有安全,性能等問(wèn)題)

每進(jìn)入一個(gè)不同的運(yùn)行環(huán)境都會(huì)創(chuàng)建一個(gè)相應(yīng)的執(zhí)行上下文(Execution Context)岗憋,那么在一段JS程序中一般都會(huì)創(chuàng)建多個(gè)執(zhí)行上下文肃晚,js引擎會(huì)以棧的方式對(duì)這些執(zhí)行上下文進(jìn)行處理,形成函數(shù)調(diào)用棧(call stack)仔戈,棧底永遠(yuǎn)是全局執(zhí)行上下文(Global Execution Context)关串,棧頂則永遠(yuǎn)是當(dāng)前執(zhí)行上下文。

2. 函數(shù)調(diào)用棧/執(zhí)行棧

調(diào)用棧监徘,也叫執(zhí)行棧晋修,具有LIFO(后進(jìn)先出)結(jié)構(gòu),用于存儲(chǔ)在代碼執(zhí)行期間創(chuàng)建的所有執(zhí)行上下文耐量。

首次運(yùn)行JS代碼時(shí)飞蚓,會(huì)創(chuàng)建一個(gè)全局執(zhí)行上下文并Push到當(dāng)前的執(zhí)行棧中。每當(dāng)發(fā)生函數(shù)調(diào)用廊蜒,引擎都會(huì)為該函數(shù)創(chuàng)建一個(gè)新的函數(shù)執(zhí)行上下文并Push到當(dāng)前執(zhí)行棧的棧頂趴拧。

當(dāng)棧頂函數(shù)運(yùn)行完成后,其對(duì)應(yīng)的函數(shù)執(zhí)行上下文將會(huì)從執(zhí)行棧中Pop出山叮,上下文控制權(quán)將移到當(dāng)前執(zhí)行棧的下一個(gè)執(zhí)行上下文著榴。

var a = 'Hello World!';

function first() {  
  console.log('Inside first function');  
  second();  
  console.log('Again inside first function');  
}

function second() {  
  console.log('Inside second function');  
}

first();  
console.log('Inside Global Execution Context');

// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context

[圖片上傳失敗...(image-bd202f-1551101247545)]

3. 執(zhí)行上下文的創(chuàng)建

執(zhí)行上下文可理解為當(dāng)前的執(zhí)行環(huán)境,與該運(yùn)行環(huán)境相對(duì)應(yīng)屁倔,具體分類如上面所說(shuō)分為全局執(zhí)行上下文和函數(shù)執(zhí)行上下文脑又。創(chuàng)建執(zhí)行上下文的三部曲:

  • 創(chuàng)建變量對(duì)象(Variable Object)

  • 建立作用域鏈(Scope Chain)

  • 確定this的指向

3.1 創(chuàng)建變量對(duì)象

創(chuàng)建變量對(duì)象
  • 創(chuàng)建arguments對(duì)象:檢查當(dāng)前上下文中的參數(shù),建立該對(duì)象的屬性與屬性值锐借,僅在函數(shù)環(huán)境(非箭頭函數(shù))中進(jìn)行问麸,全局環(huán)境沒(méi)有此過(guò)程

  • 檢查當(dāng)前上下文的函數(shù)聲明:按代碼順序查找,將找到的函數(shù)提前聲明钞翔,如果當(dāng)前上下文的變量對(duì)象沒(méi)有該函數(shù)名屬性严卖,則在該變量對(duì)象以函數(shù)名建立一個(gè)屬性,屬性值則為指向該函數(shù)所在堆內(nèi)存地址的引用布轿,如果存在哮笆,則會(huì)被新的引用覆蓋。

  • 檢查當(dāng)前上下文的變量聲明:按代碼順序查找汰扭,將找到的變量提前聲明稠肘,如果當(dāng)前上下文的變量對(duì)象沒(méi)有該變量名屬性,則在該變量對(duì)象以變量名建立一個(gè)屬性萝毛,屬性值為undefined项阴;如果存在,則忽略該變量聲明

函數(shù)聲明提前和變量聲明提升是在創(chuàng)建變量對(duì)象中進(jìn)行的笆包,且函數(shù)聲明優(yōu)先級(jí)高于變量聲明环揽。具體是如何函數(shù)和變量聲明提前的可以看后面拷沸。

創(chuàng)建變量對(duì)象發(fā)生在預(yù)編譯階段,但尚未進(jìn)入執(zhí)行階段薯演,該變量對(duì)象都是不能訪問(wèn)的,因?yàn)榇藭r(shí)的變量對(duì)象中的變量屬性尚未賦值秧了,值仍為undefined跨扮,只有進(jìn)入執(zhí)行階段,變量對(duì)象中的變量屬性進(jìn)行賦值后验毡,變量對(duì)象(Variable Object)轉(zhuǎn)為活動(dòng)對(duì)象(Active Object)后衡创,才能進(jìn)行訪問(wèn),這個(gè)過(guò)程就是VO –> AO過(guò)程晶通。

3.2 建立作用域鏈

通俗理解璃氢,作用域鏈由當(dāng)前執(zhí)行環(huán)境的變量對(duì)象(未進(jìn)入執(zhí)行階段前)與上層環(huán)境的一系列活動(dòng)對(duì)象組成,它保證了當(dāng)前執(zhí)行環(huán)境對(duì)符合訪問(wèn)權(quán)限的變量和函數(shù)的有序訪問(wèn)狮辽。

可以通過(guò)一個(gè)例子簡(jiǎn)單理解:

var num = 30;

function test() {
    var a = 10;

    function innerTest() {
        var b = 20;

        return a + b
    }

    innerTest()
}

test()

在上面的例子中一也,當(dāng)執(zhí)行到調(diào)用innerTest函數(shù),進(jìn)入innerTest函數(shù)環(huán)境喉脖。全局執(zhí)行上下文和test函數(shù)執(zhí)行上下文已進(jìn)入執(zhí)行階段椰苟,innerTest函數(shù)執(zhí)行上下文在預(yù)編譯階段創(chuàng)建變量對(duì)象,所以他們的活動(dòng)對(duì)象和變量對(duì)象分別是AO(global)树叽,AO(test)和VO(innerTest)舆蝴,而innerTest的作用域鏈由當(dāng)前執(zhí)行環(huán)境的變量對(duì)象(未進(jìn)入執(zhí)行階段前)與上層環(huán)境的一系列活動(dòng)對(duì)象組成,如下:

innerTestEC = {

    //變量對(duì)象
    VO: {b: undefined}, 

    //作用域鏈
    scopeChain: [VO(innerTest), AO(test), AO(global)],  
    
    //this指向
    this: window
}

深入理解的話题诵,創(chuàng)建作用域鏈洁仗,也就是創(chuàng)建詞法環(huán)境,而詞法環(huán)境有兩個(gè)組成部分:

  • 環(huán)境記錄:存儲(chǔ)變量和函數(shù)聲明的實(shí)際位置
  • 對(duì)外部環(huán)境的引用:可以訪問(wèn)其外部詞法環(huán)境

詞法環(huán)境類型偽代碼如下:

// 第一種類型: 全局環(huán)境
GlobalExectionContext = {  // 全局執(zhí)行上下文
  LexicalEnvironment: {       // 詞法環(huán)境
    EnvironmentRecord: {        // 環(huán)境記錄
      Type: "Object",              // 全局環(huán)境
      // 標(biāo)識(shí)符綁定在這里 
      outer: <null>                // 對(duì)外部環(huán)境的引用
  }  
}

// 第二種類型: 函數(shù)環(huán)境
FunctionExectionContext = { // 函數(shù)執(zhí)行上下文
  LexicalEnvironment: {       // 詞法環(huán)境
    EnvironmentRecord: {        // 環(huán)境記錄
      Type: "Declarative",         // 函數(shù)環(huán)境
      // 標(biāo)識(shí)符綁定在這里             // 對(duì)外部環(huán)境的引用
      outer: <Global or outer function environment reference>  
  }  
}

在創(chuàng)建變量對(duì)象性锭,也就是創(chuàng)建變量環(huán)境赠潦,而變量環(huán)境也是一個(gè)詞法環(huán)境。在 ES6 中篷店,詞法 環(huán)境和 變量 環(huán)境的區(qū)別在于前者用于存儲(chǔ)函數(shù)聲明和變量( letconst )綁定祭椰,而后者僅用于存儲(chǔ)變量( var )綁定。

如例子:

let a = 20;  
const b = 30;  
var c;

function multiply(e, f) {  
 var g = 20;  
 return e * f * g;  
}

c = multiply(20, 30);

執(zhí)行上下文如下所示

GlobalExectionContext = {

  ThisBinding: <Global Object>,

  LexicalEnvironment: {  
    EnvironmentRecord: {  
      Type: "Object",  
      // 標(biāo)識(shí)符綁定在這里  
      a: < uninitialized >,  
      b: < uninitialized >,  
      multiply: < func >  
    }  
    outer: <null>  
  },

  VariableEnvironment: {  
    EnvironmentRecord: {  
      Type: "Object",  
      // 標(biāo)識(shí)符綁定在這里  
      c: undefined,  
    }  
    outer: <null>  
  }  
}

FunctionExectionContext = {  
   
  ThisBinding: <Global Object>,

  LexicalEnvironment: {  
    EnvironmentRecord: {  
      Type: "Declarative",  
      // 標(biāo)識(shí)符綁定在這里  
      Arguments: {0: 20, 1: 30, length: 2},  
    },  
    outer: <GlobalLexicalEnvironment>  
  },

  VariableEnvironment: {  
    EnvironmentRecord: {  
      Type: "Declarative",  
      // 標(biāo)識(shí)符綁定在這里  
      g: undefined  
    },  
    outer: <GlobalLexicalEnvironment>  
  }  
}

變量提升的具體原因:在創(chuàng)建階段疲陕,函數(shù)聲明存儲(chǔ)在環(huán)境中方淤,而變量會(huì)被設(shè)置為 undefined(在 var 的情況下)或保持未初始化(在 letconst 的情況下)。所以這就是為什么可以在聲明之前訪問(wèn) var 定義的變量(盡管是 undefined )蹄殃,但如果在聲明之前訪問(wèn) letconst 定義的變量就會(huì)提示引用錯(cuò)誤的原因携茂。此時(shí)let 和 const處于未初始化狀態(tài)不能使用,只有進(jìn)入執(zhí)行階段诅岩,變量對(duì)象中的變量屬性進(jìn)行賦值后讳苦,變量對(duì)象(Variable Object)轉(zhuǎn)為活動(dòng)對(duì)象(Active Object)后带膜,letconst才能進(jìn)行訪問(wèn)。

關(guān)于函數(shù)聲明和變量聲明鸳谜,這篇文章講的很好: https://github.com/yygmind/blog/issues/13

另外關(guān)于閉包的理解膝藕,如例子:

function foo() {
    var num = 20;

    function bar() {
        var result = num + 20;

        return result
    }

    bar()
}

foo()

瀏覽器分析如下:

[站外圖片上傳中...(image-3d274e-1551101247545)]

chrome瀏覽器理解閉包是foo,那么按瀏覽器的標(biāo)準(zhǔn)是如何定義閉包的咐扭,總結(jié)為三點(diǎn):

  • 在函數(shù)內(nèi)部定義新函數(shù)

  • 新函數(shù)訪問(wèn)外層函數(shù)的局部變量芭挽,即訪問(wèn)外層函數(shù)環(huán)境的活動(dòng)對(duì)象屬性

  • 新函數(shù)執(zhí)行,創(chuàng)建新的函數(shù)執(zhí)行上下文蝗肪,外層函數(shù)即為閉包

3.3 this指向

比較復(fù)雜袜爪,后面專門弄一篇文章來(lái)整理。

后面的內(nèi)容請(qǐng)往這里走 JS引擎線程的執(zhí)行過(guò)程的三個(gè)階段(二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末薛闪,一起剝皮案震驚了整個(gè)濱河市辛馆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌豁延,老刑警劉巖昙篙,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異术浪,居然都是意外死亡瓢对,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門胰苏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)硕蛹,“玉大人,你說(shuō)我怎么就攤上這事硕并》ㄑ妫” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵倔毙,是天一觀的道長(zhǎng)埃仪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)陕赃,這世上最難降的妖魔是什么卵蛉? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮么库,結(jié)果婚禮上傻丝,老公的妹妹穿的比我還像新娘。我一直安慰自己诉儒,他們只是感情好葡缰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般泛释。 火紅的嫁衣襯著肌膚如雪滤愕。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天怜校,我揣著相機(jī)與錄音间影,去河邊找鬼。 笑死茄茁,一個(gè)胖子當(dāng)著我的面吹牛宇智,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胰丁,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼喂分!你這毒婦竟也來(lái)了锦庸?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蒲祈,失蹤者是張志新(化名)和其女友劉穎甘萧,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體梆掸,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扬卷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酸钦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怪得。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖卑硫,靈堂內(nèi)的尸體忽然破棺而出徒恋,到底是詐尸還是另有隱情,我是刑警寧澤欢伏,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布入挣,位于F島的核電站,受9級(jí)特大地震影響硝拧,放射性物質(zhì)發(fā)生泄漏径筏。R本人自食惡果不足惜霎苗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一膜楷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秦效,春花似錦咸这、人聲如沸夷恍。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)酿雪。三九已至遏暴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間指黎,已是汗流浹背朋凉。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留醋安,地道東北人杂彭。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像吓揪,于是被迫代替她去往敵國(guó)和親亲怠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,089評(píng)論 1 32
  • 一切皆對(duì)象 js中的一個(gè)常見(jiàn)運(yùn)算符 typeof 以上代碼列出了 typeof 輸出的集中類型標(biāo)識(shí)柠辞, 其中上面的四...
    無(wú)跡落花閱讀 1,985評(píng)論 0 5
  • 去年春節(jié)回老家团秽,見(jiàn)到表妹,表妺上班幾年了還沒(méi)談男朋友叭首,她父母很著急习勤,但表妹看似一點(diǎn)也不著急,當(dāng)父母的急得不得...
    Lys初心閱讀 449評(píng)論 0 3
  • 類似的話題聊過(guò)好多次了予颤,這次分幾個(gè)檔位,聊下具體的投資方案冬阳,看起來(lái)也更直接荣瑟。 說(shuō)在前面的是:因?yàn)槊總€(gè)人的風(fēng)險(xiǎn)承受能...
    萌萌有神閱讀 4,542評(píng)論 9 36
  • 我曾經(jīng)很羨慕那些能說(shuō)會(huì)道的笆焰,還有會(huì)演講的人,聽(tīng)他們演講见坑,總會(huì)給我一些感觸和正能量嚷掠。但是,我們經(jīng)常也會(huì)聽(tīng)到一些不好的...
    徐慕熹微閱讀 808評(píng)論 4 11