理解JavaScript運(yùn)行機(jī)制(單線程、異步模式)

在之前的博客中忌卤,我們認(rèn)識(shí)了瀏覽器是如何渲染頁(yè)面的扫夜?。今天來(lái)學(xué)習(xí)JavaScript在瀏覽器中的運(yùn)行機(jī)制驰徊。

瀏覽器的渲染進(jìn)程是多線程的

  1. GUI渲染進(jìn)程
    • 負(fù)責(zé)渲染瀏覽器界面笤闯,解析HTML,CSS棍厂,構(gòu)建DOM樹(shù)和RenderObject樹(shù)颗味,布局和繪制等
    • 當(dāng)界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時(shí),該線程就會(huì)執(zhí)行
    • GUI渲染線程與JS引擎線程是互斥的牺弹,當(dāng)JS引擎執(zhí)行時(shí)GUI線程會(huì)被掛起浦马,GUI更新會(huì)被保存在一個(gè)隊(duì)列中等到JS引擎空閑時(shí)立即被執(zhí)行。
  2. JS引擎線程
    • JS引擎線程負(fù)責(zé)解析Javascript腳本张漂,運(yùn)行代碼晶默。
    • JS是單線程的
    • GUI渲染線程與JS引擎線程是互斥的,所以如果JS執(zhí)行的時(shí)間過(guò)長(zhǎng)航攒,這樣就會(huì)造成頁(yè)面的渲染不連貫磺陡,導(dǎo)致頁(yè)面渲染加載阻塞。
  3. 事件觸發(fā)線程
    • 歸屬于瀏覽器而不是JS引擎,用來(lái)控制事件循環(huán)
    • 當(dāng)JS引擎執(zhí)行代碼塊如setTimeOut時(shí)(也可來(lái)自瀏覽器內(nèi)核的其他線程,如鼠標(biāo)點(diǎn)擊币他、AJAX異步請(qǐng)求等)坞靶,會(huì)將對(duì)應(yīng)任務(wù)添加到事件線程中
    • 當(dāng)對(duì)應(yīng)的事件符合觸發(fā)條件被觸發(fā)時(shí),該線程會(huì)把事件添加到待處理隊(duì)列的隊(duì)尾蝴悉,等待JS引擎的處理
    • 由于JS的單線程關(guān)系滩愁,所以這些待處理隊(duì)列中的事件都得排隊(duì)等待JS引擎處理

JavaScript是單線程的

單線程模型指的是,JavaScript 只在一個(gè)線程上運(yùn)行辫封。也就是說(shuō)硝枉,JavaScript 同時(shí)只能執(zhí)行一個(gè)任務(wù),其他任務(wù)都必須在后面排隊(duì)等待倦微。
這種模式的好處是實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單妻味,執(zhí)行環(huán)境相對(duì)單純;壞處是只要有一個(gè)任務(wù)耗時(shí)很長(zhǎng)欣福,后面的任務(wù)都必須排隊(duì)等著责球,會(huì)拖延整個(gè)程序的執(zhí)行。比如等待 Ajax 請(qǐng)求返回結(jié)果拓劝。這個(gè)時(shí)候雏逾,如果對(duì)方服務(wù)器遲遲沒(méi)有響應(yīng),或者網(wǎng)絡(luò)不通暢郑临,就會(huì)導(dǎo)致腳本的長(zhǎng)時(shí)間停滯栖博。
JavaScript 內(nèi)部采用的“事件循環(huán)”機(jī)制(Event Loop)。這時(shí) CPU 完全可以不管 IO 操作厢洞,掛起處于等待中的任務(wù)仇让,先運(yùn)行排在后面的任務(wù)。等到 IO 操作返回了結(jié)果躺翻,再回過(guò)頭丧叽,把掛起的任務(wù)繼續(xù)執(zhí)行下去。這種機(jī)制就是 JavaScript 內(nèi)部采用的“事件循環(huán)”機(jī)制(Event Loop)

任務(wù)隊(duì)列和事件循環(huán)

JavaScript 運(yùn)行時(shí)公你,除了一個(gè)正在運(yùn)行的主線程踊淳,引擎還提供一個(gè)任務(wù)隊(duì)列(task queue),里面是各種需要當(dāng)前程序處理的異步任務(wù)陕靠。
首先迂尝,主線程會(huì)去執(zhí)行所有的同步任務(wù)。等到同步任務(wù)全部執(zhí)行完懦傍,就會(huì)去看任務(wù)隊(duì)列里面的異步任務(wù)雹舀。如果滿足條件,那么異步任務(wù)就重新進(jìn)入主線程開(kāi)始執(zhí)行粗俱,這時(shí)它就變成同步任務(wù)了说榆。等到執(zhí)行完,下一個(gè)異步任務(wù)再進(jìn)入主線程開(kāi)始執(zhí)行。一旦任務(wù)隊(duì)列清空签财,程序就結(jié)束執(zhí)行串慰。

異步任務(wù)的寫法通常是回調(diào)函數(shù)。一旦異步任務(wù)重新進(jìn)入主線程唱蒸,就會(huì)執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)邦鲫。如果一個(gè)異步任務(wù)沒(méi)有回調(diào)函數(shù),就不會(huì)進(jìn)入任務(wù)隊(duì)列神汹,也就是說(shuō)庆捺,不會(huì)重新進(jìn)入主線程,因?yàn)闆](méi)有用回調(diào)函數(shù)指定下一步的操作屁魏。

JavaScript 引擎怎么知道異步任務(wù)有沒(méi)有結(jié)果滔以,能不能進(jìn)入主線程呢?答案就是引擎在不停地檢查氓拼,一遍又一遍你画,只要同步任務(wù)執(zhí)行完了,引擎就會(huì)去檢查那些掛起來(lái)的異步任務(wù)桃漾,是不是可以進(jìn)入主線程了坏匪。這種循環(huán)檢查的機(jī)制,就叫做事件循環(huán)(Event Loop)撬统。

異步操作的模式

  1. 回調(diào)函數(shù)
    下面是兩個(gè)函數(shù)f1和f2适滓,編程的意圖是f2必須等到f1執(zhí)行完成,才能執(zhí)行宪摧。
function f1(){
  //...
}
function f2(){
  //...
}
f1();
f2();

但是如果f1是異步操作粒竖,f2會(huì)立即執(zhí)行颅崩,不會(huì)等到f1結(jié)束再執(zhí)行几于。為了達(dá)到同一目的,我們可以用回調(diào)函數(shù)改寫

function f1(callback){
 //...
callback();
}
function f2(){
 //...
}
f1(f2)

回調(diào)函數(shù)的優(yōu)點(diǎn)是簡(jiǎn)單沿后、容易理解和實(shí)現(xiàn)沿彭,缺點(diǎn)是不利于代碼的閱讀和維護(hù),各個(gè)部分之間高度耦合尖滚,使得程序結(jié)構(gòu)混亂喉刘、流程難以追蹤(尤其是多個(gè)回調(diào)函數(shù)嵌套的情況),而且每個(gè)任務(wù)只能指定一個(gè)回調(diào)函數(shù)漆弄。

  1. 事件監(jiān)聽(tīng)
    另一種思路是采用事件驅(qū)動(dòng)模式睦裳。異步任務(wù)的執(zhí)行不取決于代碼的順序,而取決于某個(gè)事件是否發(fā)生撼唾。

還是以f1f2為例廉邑。首先,為f1綁定一個(gè)事件(這里采用的 jQuery 的寫法

f1.on('done', f2);

上面這行代碼的意思是,當(dāng)f1發(fā)生done事件蛛蒙,就執(zhí)行f2糙箍。然后,對(duì)f1進(jìn)行改寫:

function f1() {
  setTimeout(function () {
    // ...
    f1.trigger('done');
  }, 1000);
}

上面代碼中牵祟,f1.trigger('done')表示深夯,執(zhí)行完成后,立即觸發(fā)done事件诺苹,從而開(kāi)始執(zhí)行f2咕晋。

這種方法的優(yōu)點(diǎn)是比較容易理解,可以綁定多個(gè)事件收奔,每個(gè)事件可以指定多個(gè)回調(diào)函數(shù)捡需,而且可以去耦合,有利于實(shí)現(xiàn)模塊化筹淫。缺點(diǎn)是整個(gè)程序都要變成事件驅(qū)動(dòng)型站辉,運(yùn)行流程會(huì)變得很不清晰。閱讀代碼的時(shí)候损姜,很難看出主流程饰剥。

定時(shí)器的運(yùn)行機(jī)制

setTimeout和setInterval的運(yùn)行機(jī)制,是將指定的代碼移出本輪事件循環(huán)摧阅,等到下一輪事件循環(huán)汰蓉,再檢查是否到了指定時(shí)間。如果到了棒卷,就執(zhí)行對(duì)應(yīng)的代碼顾孽;如果不到,就繼續(xù)等待比规。

這意味著毕谴,setTimeout和setInterval指定的回調(diào)函數(shù),必須等到本輪事件循環(huán)的所有同步任務(wù)都執(zhí)行完懊蒸,才會(huì)開(kāi)始執(zhí)行兴使。由于前面的任務(wù)到底需要多少時(shí)間執(zhí)行完,是不確定的灾常,所以沒(méi)有辦法保證霎冯,setTimeout和setInterval指定的任務(wù),一定會(huì)按照預(yù)定時(shí)間執(zhí)行钞瀑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沈撞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雕什,更是在濱河造成了極大的恐慌缠俺,老刑警劉巖拧廊,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異晋修,居然都是意外死亡吧碾,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門墓卦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)倦春,“玉大人,你說(shuō)我怎么就攤上這事落剪≌霰荆” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵忠怖,是天一觀的道長(zhǎng)呢堰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)凡泣,這世上最難降的妖魔是什么枉疼? 我笑而不...
    開(kāi)封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮鞋拟,結(jié)果婚禮上骂维,老公的妹妹穿的比我還像新娘。我一直安慰自己贺纲,他們只是感情好航闺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著猴誊,像睡著了一般潦刃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上懈叹,一...
    開(kāi)封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天乖杠,我揣著相機(jī)與錄音,去河邊找鬼项阴。 笑死滑黔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的环揽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼庵佣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼歉胶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起巴粪,我...
    開(kāi)封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤通今,失蹤者是張志新(化名)和其女友劉穎粥谬,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體辫塌,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漏策,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了臼氨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掺喻。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖储矩,靈堂內(nèi)的尸體忽然破棺而出感耙,到底是詐尸還是另有隱情,我是刑警寧澤持隧,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布即硼,位于F島的核電站,受9級(jí)特大地震影響屡拨,放射性物質(zhì)發(fā)生泄漏只酥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一呀狼、第九天 我趴在偏房一處隱蔽的房頂上張望层皱。 院中可真熱鬧,春花似錦赠潦、人聲如沸叫胖。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瓮增。三九已至,卻和暖如春哩俭,著一層夾襖步出監(jiān)牢的瞬間绷跑,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工凡资, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留砸捏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓隙赁,卻偏偏與公主長(zhǎng)得像垦藏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子伞访,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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