了解JS單線程和任務隊列兜辞!

一、單線程和任務隊列

  • 單線程就意味著夸溶,所有任務需要排隊逸吵,前一個任務結(jié)束,才會執(zhí)行后一個任務缝裁。如果前一個任務耗時很長扫皱,后一個任務就不得不一直等待。
  • 如果排隊是因為計算量過大捷绑,CPU忙不過來韩脑,倒也算了,但是很多時候CPU是閑著的粹污,因為IO設備(輸入輸出設備)很慢(比如Ajax操作從網(wǎng)絡讀取數(shù)據(jù))段多,不得不等著結(jié)果出來,再往下執(zhí)行!
  • JavaScript語言的設計者意識到壮吩,這時主線程完全可以不管IO設備进苍,掛起處于等待中的任務加缘,先運行排在后邊的任務,等到IO設備返回了結(jié)果觉啊,再回過頭把掛起的任務繼續(xù)執(zhí)行下去拣宏。
  • 于是,所有的任務可以分為兩種杠人,一種是同步任務(synchronous)勋乾,另外一種是異步任務(asynchronous)。同步任務指的是嗡善,在主線程上辑莫,排隊執(zhí)行的任務,只有前一個任務執(zhí)行完畢滤奈,才能執(zhí)行后一個任務;異步任務指的是摆昧,不進入主線程,而進入“任務隊列”(task queue)的任務蜒程,只有“任務隊列”通知主線程绅你,某個異步任務可以執(zhí)行了,該任務才會進入主線程執(zhí)行昭躺。

具體來說忌锯,異步執(zhí)行的運行機制如下(同步執(zhí)行也是如此,因為它可以被視為沒有異步任務的異步執(zhí)行)

1. 所有同步任務都在主線程上執(zhí)行领炫,形成一個執(zhí)行棧(execution context stack)偶垮。
2. 主線程之外,還存在一個“任務隊列”(task queue)帝洪,只要異步 
   任務有了運行結(jié)果似舵,就在“任務隊列”中放置一個事件。
3. 一旦“執(zhí)行棿邢浚”中的所有同步任務執(zhí)行完畢砚哗,系統(tǒng)就會讀取“任務隊      
   列”,看看里邊有哪些事件砰奕。哪些對應的異步任務蛛芥,于是結(jié)束等待 
   狀態(tài),進入“執(zhí)行椌”開始執(zhí)行仅淑。
4. 主線程不斷重復上邊的第三步。  

下邊就是主線程和任務隊列的示意圖:

image.png

主要主線程空了胸哥,就會去讀取“任務隊列”涯竟,這就是JavaScript的運行機制,這個過程會不斷重復。

二昆禽、事件和回調(diào)函數(shù)

  • “任務隊列”是一個事件的隊列(也可以理解成消息的隊列)蝗蛙,IO設備完成一項任務,就在“任務隊列”中添加一個事件醉鳖,表示相關(guān)的異步任務可以進入“執(zhí)行椉窆瑁”了,主線程讀取“任務隊列”盗棵,即使讀取里邊有哪些事件壮韭。
  • “任務隊列”里邊的事件,除了IO設備的事件以外纹因,還包括一些用戶產(chǎn)生的事件(比如鼠標點擊喷屋,頁面滾動等等)。只要指定過回調(diào)函數(shù)瞭恰,這些事件發(fā)生時屯曹,就會進入任務隊列,等待主線程讀取惊畏。
  • 所謂“回調(diào)函數(shù)”(callback)恶耽,就是那些會被主線程掛起來的代碼。異步任務必須執(zhí)行回調(diào)函數(shù)颜启,當主線程開始執(zhí)行異步任務偷俭,就是執(zhí)行對應的回調(diào)函數(shù)。
  • “任務隊列”是一個先進先出的數(shù)據(jù)結(jié)構(gòu)缰盏,排在前邊的事件涌萤,優(yōu)先被主線程讀取。主線程的讀取過程口猜,基本上市自動的负溪,只要“執(zhí)行棧”一清空济炎,“任務隊列”上第一位的事件笙以,就會自動進入主線程。但是由于存在后文存提到的“定時器”功能冻辩,主線程首先要檢查一下執(zhí)行時間,某些事件拆祈,只有到了規(guī)定時間恨闪,才能返回主線程。

三放坏、Event Loop

  • 主線程從“任務隊列”中讀取事件咙咽,這個過程是循環(huán)不斷的,所以整個的這種運行機制淤年,又稱為Event Loop (事件循環(huán))钧敞。
    為了更好地理解Event Loop蜡豹,請看下圖(轉(zhuǎn)引自Philip Roberts的演講《Help, I'm stuck in an event-loop》)。
    image.png

上圖中溉苛,主線程運行的時候镜廉,產(chǎn)生堆(heap)和棧(stack),棧中的代碼調(diào)用各種外部API愚战,它們在任務隊列中加入各種事件(click娇唯,load,done)寂玲。只要棧中的代碼執(zhí)行完畢塔插,主線程就會去讀取“任務隊列”,依次執(zhí)行那些事件拓哟,所對應的回調(diào)函數(shù)想许。
執(zhí)行棧中的代碼(同步任務),總是在讀取“任務隊列”(異步任務)之前執(zhí)行断序,請看下邊的例子:

var req = new XMLHttpRequest();
    req.open('GET', url);    
    req.onload = function (){};    
    req.onerror = function (){};    
    req.send();

上面代碼中的req.send方法是Ajax操作向服務器發(fā)送數(shù)據(jù)流纹,它是一個異步任務,意味著只有當前腳本的所有代碼執(zhí)行完逢倍,系統(tǒng)才會去讀取"任務隊列"捧颅。所以,它與下面的寫法等價较雕。

var req = new XMLHttpRequest();
    req.open('GET', url);
    req.send();
    req.onload = function (){};    
    req.onerror = function (){};   

也就是說碉哑,指定回調(diào)函數(shù)的部分(onload和onerror),在send()方法的前面或后面無關(guān)緊要亮蒋,因為它們屬于執(zhí)行棧的一部分扣典,系統(tǒng)總是執(zhí)行完它們,才會去讀取"任務隊列"慎玖。

四贮尖、定時器

  • 除了放置異步任務的事件,“任務隊列”還可以放置定時事件趁怔,即指定某些代碼湿硝,在多少時間后執(zhí)行。這叫做定時器功能(timer),也就是定時執(zhí)行的代碼润努。
  • 定時器功能主要由 setTimeout()和setInterval()這兩個函數(shù)來完成关斜,它們的內(nèi)部運行機制完全一樣,區(qū)別在于前者指定的代碼是一次性執(zhí)行铺浇,后者則為反復執(zhí)行痢畜,以下主要討論setTimeout()。
    setTimeout()接受兩個參數(shù),第一個是回調(diào)函數(shù)丁稀,第二個是推遲執(zhí)行的毫秒數(shù)吼拥。
console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);

上面代碼的執(zhí)行結(jié)果是1,3线衫,2凿可,因為setTimeout()將第二行推遲到1000毫秒之后執(zhí)行。
如果將setTimeout()的第二個參數(shù)設為0桶雀,就表示當前代碼執(zhí)行完(執(zhí)行棧清空)以后矿酵,立即執(zhí)行(0毫秒間隔)指定的回調(diào)函數(shù)。

setTimeout(function(){console.log(1);}, 0);
console.log(2);

上面代碼的執(zhí)行結(jié)果總是2矗积,1全肮,因為只有在執(zhí)行完第二行以后,系統(tǒng)才會去執(zhí)行"任務隊列"中的回調(diào)函數(shù)棘捣。

  • 總之辜腺,setTimeout(fn,0)的含義是,指定某個任務乍恐,在主線程最早可得的空閑時間執(zhí)行评疗,也就是說,盡可能早的執(zhí)行茵烈。它在“任務隊列”的尾部添加一個事件百匆,因此要等到同步任務和“任務隊列”中的現(xiàn)有事件,都處理完呜投,才會得到執(zhí)行加匈。
  • HTML5標準規(guī)定了setTimeout()的第二個參數(shù)的最小值(最短間隔),不得低于4毫秒仑荐,如果低于這個值雕拼,就會自動增加。在此之前粘招,老版本的瀏覽器都將最短間隔設為10毫秒啥寇。另外,對于那些DOM的變動(尤其是涉及頁面重新渲染的部分)洒扎,通常不會立即執(zhí)行辑甜,而是每16毫秒執(zhí)行一次。這時使用requestAnimationFrame()的效果要好于setTimeout()袍冷。
  • 需要注意的是磷醋,setTimeout()只是將事件插入了"任務隊列",必須等到當前代碼(執(zhí)行棧)執(zhí)行完难裆,主線程才會去執(zhí)行它指定的回調(diào)函數(shù)。要是當前代碼耗時很長,有可能要等很久乃戈,所以并沒有辦法保證褂痰,回調(diào)函數(shù)一定會在setTimeout()指定的時間執(zhí)行。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末症虑,一起剝皮案震驚了整個濱河市缩歪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谍憔,老刑警劉巖匪蝙,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異习贫,居然都是意外死亡逛球,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門苫昌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颤绕,“玉大人,你說我怎么就攤上這事祟身“挛瘢” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵袜硫,是天一觀的道長氯葬。 經(jīng)常有香客問我,道長婉陷,這世上最難降的妖魔是什么帚称? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮憨攒,結(jié)果婚禮上世杀,老公的妹妹穿的比我還像新娘。我一直安慰自己肝集,他們只是感情好瞻坝,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杏瞻,像睡著了一般所刀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捞挥,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天野舶,我揣著相機與錄音烹植,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的排作。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼煌抒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起厕倍,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤寡壮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后讹弯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體况既,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年组民,在試婚紗的時候發(fā)現(xiàn)自己被綠了棒仍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡邪乍,死狀恐怖降狠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庇楞,我是刑警寧澤榜配,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站吕晌,受9級特大地震影響蛋褥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜睛驳,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一烙心、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乏沸,春花似錦淫茵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蝶缀,卻和暖如春丹喻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背翁都。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工碍论, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柄慰。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓鳍悠,卻偏偏與公主長得像税娜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子藏研,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

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