從 setTimeout 到事件循環(huán)

回想起第一次使用 setTimeout 函數(shù)的時候,設(shè)置的回調(diào)函數(shù)并不是立即執(zhí)行衡蚂,而是過了一段定義好的時間之后才執(zhí)行喇勋,這讓當(dāng)時剛知道 Javascript 是單線程的我無法理解酗钞,誤以為“單線程”的定義是錯的岸裙。過了段時間的學(xué)習(xí)之后才知道這玩意叫“異步”。

代碼是從上往下一行一行地執(zhí)行旷偿,為什么類似這樣的異步的東西烹俗,卻“違反常規(guī)”,執(zhí)行到的時候會“忽略”萍程,跳到最后的時刻才執(zhí)行幢妄。換句話來說,同步代碼執(zhí)行完畢之后茫负,異步的東西才開始執(zhí)行蕉鸳。

結(jié)合堆棧來看在 Javascript 中代碼是如何執(zhí)行的

同步代碼

function foo() {
  console.log( 'foo' )
}

function bar() {
  foo()
  console.log( 'bar )
}

bar()

/*
 * 結(jié)果:
 * 'foo'
 * 'bar'
 */

代碼執(zhí)行到 bar() 的時候,對應(yīng)的堆棧情況為為下圖:

bar()
foo()
console.log( 'foo' )

此時輸出 'foo'朽褪,console.log( 'foo' ) 執(zhí)行完畢之后置吓,從棧中彈出,此時 foo 也執(zhí)行完畢缔赠,foo 也從棧中彈出,往下執(zhí)行到 console.log( 'bar ):

console.log( 'bar' )

在這里執(zhí)行 console.log( 'bar ) 之后輸出 'bar'友题,之后彈出嗤堰,bar 執(zhí)行完畢也彈出,棧空踢匣。

setTimeout 代碼

function bar() {
  setTimeout( () => {
    console.log( 'foo' )
  }, 2000 )

  console.log( 'bar' )
}

bar()

/*
 * 結(jié)果:
 * 'bar'
 * 'foo'
 */

代碼執(zhí)行到 bar() 的時候告匠,對應(yīng)的堆棧情況為為下圖:

bar()
setTimeout()

setTimeout() 執(zhí)行之前,先說明 setTimeout 有關(guān)的東西离唬。

理解 setTimeout

setTimeout 是屬于 window 上的一個方法后专,在 ECMA-262規(guī)范 中找不到關(guān)于 setTimeout 的定義,但在 HTML規(guī)范 中找到了有關(guān)定義输莺。所以 setTimeout 是一個 DOM API 戚哎,是瀏覽器對其內(nèi)部進(jìn)行實現(xiàn)的(可能是 webkit),而非 js引擎(例如 V8)實現(xiàn)嫂用,然后掛在 window 全局屬性(window.setTimeout)上供 Javascript 訪問調(diào)用型凳。

具體來說是瀏覽器實現(xiàn)了一個 timer_handler,叫做“定時器觀察者”這么一個東西嘱函。其實瀏覽器實現(xiàn)了幾個 handler甘畅,比如發(fā)起 ajax 請求的 xhr_handler 等......瀏覽器對這個觀察者向 Javascript 提供了一個 API(setTimeout) 用來傳遞被觀察的對象進(jìn)去。也就是說 Javascript 在調(diào)用 setTimeout 的時候往弓,是往 timer_handler 傳遞了一個 timer 對象進(jìn)去疏唾,timer_handler 在收到這個對象之后,Javascript 對 setTimeout 的同步調(diào)用已經(jīng)結(jié)束函似,立即返回槐脏。可以說是一次 V8 到 webkit 的過程缴淋,即 Javascript 到 DOM API 的過程准给。

被傳遞的觀察者類似于 timer = { time: 2000ms, callback: function() {...} },Javascript 通過 setTimeout 將 timer 傳遞給 timer_handler重抖。

觀察者在收到 timer 之后露氮,就會一直監(jiān)控時間,如果到達(dá)觸發(fā)的條件钟沛,就會將 timer 推入一個隊列中畔规,等待 Javascript 主線程空閑之后執(zhí)行

setTimeout 的交流

回到上面 setTimeout () 的調(diào)用那里繼續(xù),此時增加一個 handlers 和一個 queue隊列恨统。

setTimeout 調(diào)用過程

setTimeout()

setTimeout () 調(diào)用之后叁扫,會有一個 timer 對象被傳遞到 handlers 中的 timer_handler。

傳遞 timer

在 timer 傳遞完畢之后畜埋,對于 setTimeout 的調(diào)用結(jié)束莫绣,立即返回,setTimeout 出棧

setTimeout 出棧

與此同時悠鞍,timer_handler 會對該 aTimer 不停地進(jìn)行監(jiān)控对室,看是否達(dá)到時間觸發(fā)。由于之前設(shè)置了 2000ms,因此沒那么快觸發(fā)掩宜。handlers 與 stack 互不影響蔫骂,互不阻塞。

接著執(zhí)行到 console.log( 'bar' )牺汤,入棧辽旋。輸出 'bar'

console.log( 'bar' )

之后 console.log( 'bar' ) 出棧,bar 調(diào)用結(jié)束檐迟,bar 出棧补胚。與此同時,timer_handler 會對該 aTimer 不停地進(jìn)行監(jiān)控锅减,看是否達(dá)到時間觸發(fā)糖儡。由于之前設(shè)置了 2000ms,因此沒那么快觸發(fā)怔匣。handlers 與 stack 互不影響握联,互不阻塞。

bar 出棧

假設(shè)自 setTimeout 的調(diào)用開始到 bar 出棧每瞒,經(jīng)歷了 1000ms金闽,那么對于 aTimer 的 2000ms 來說,還有 1000ms剿骨,因此 timer_handler 還會繼續(xù)監(jiān)控代芜。

(1000ms過后)

timer_handler 監(jiān)控到 aTimer 的時間到了,于是會將 aTimer 的 callback 推入 queue 中浓利,然后將 aTimer 從 handler 中移除挤庇。

移除 aTimer,將 aCallback 推入 queue

此時 aCallback 處于 queue 中贷掖,而 Javascript 主線程又是處于空閑狀態(tài)嫡秕,因此 aCallback 會被立即出隊,進(jìn)入主線程執(zhí)行苹威。

aCallback 出隊昆咽,進(jìn)入主線程執(zhí)行

console.log( 'foo' )執(zhí)行,輸出 'foo'

console.log( 'foo' )

執(zhí)行完畢牙甫,console.log( 'foo' ) 出棧掷酗,aCallback() 出棧。椏卟福空泻轰。

棧空

以上就是事件循環(huán)的一個過程且轨。事件循環(huán)不是某個函數(shù)或者部分 而是一套機(jī)制 這套機(jī)制的總稱叫事件循環(huán)糕殉。

事件循環(huán)

在此過程中亩鬼,handlers會一直監(jiān)控名下所有的handler殖告,只要達(dá)到觸發(fā)條件阿蝶,就會形成一個任務(wù)推入 queue 中。而在 queue 中黄绩,會有一個類似 while循環(huán) 一直讀取 queue 中的任務(wù)羡洁,也一直監(jiān)控主線程是否空閑,如果空閑爽丹,而且 queue 中存在任務(wù)筑煮,就會取出隊列頭的任務(wù)出來,推入主線程執(zhí)行粤蝎。若主線程忙碌真仲,就會等待主線程空閑。

通俗解釋

小明要做一件事初澎,就是要走完一條10米長的路秸应,這條路上他需要做一些事。他從0米開始起步碑宴。走到2米處的時候软啼,看到一個綠色呼啦圈,他停下來了延柠,拿起綠色的呼啦圈開始轉(zhuǎn)了起來祸挪,轉(zhuǎn)了一段時間后,他繼續(xù)走贞间。走到了5米處的時候贿条,他看到了一個“待辦事項”的牌子,上面寫有一些任務(wù)增热,于是他停下來整以,拿起筆和紙,在紙上寫下了一段字:“時間:5秒后钓葫,任務(wù):大喊一聲‘旺旺’悄蕾,次數(shù):1〈「。”他把紙放到了路邊的一個人的手里帆调,這個人穿著黑色的衣服,衣服上寫著“定時管理”豆同,不在小明走的這條路中番刊,而在路的旁邊。交到這個人手里之后影锈,小明開始邁開腳步芹务,繼續(xù)往前走蝉绷。走到9米處的時候,看到一個紅色呼啦圈枣抱,他停下來轉(zhuǎn)了一段時間后又繼續(xù)走熔吗。走著就就走到了第10米,完成了走路佳晶。
而剛才那個黑衣人自打收到紙條之后桅狠,就開始計算時間。從收到紙條那一刻開始轿秧,0秒中跌、1秒、2秒......5秒菇篡。好了漩符,5秒的時間到了,這個黑衣人該準(zhǔn)備做點事情了驱还。只見這個黑衣人站起身嗜暴,走到一個盒子旁邊,將紙條放到盒子里面铝侵。放進(jìn)去之后灼伤,黑衣人手里就什么都沒有了。
然而咪鲜,還有一個人狐赡,他負(fù)責(zé)管理這個盒子,他也不在那條路上疟丙,也是在路旁邊颖侄,他只做一件事,就是不停地看著那個盒子享郊。如果盒子里面有若干張紙條览祖,他就會拿出最早放進(jìn)去的那張;如果只有一張紙條炊琉,當(dāng)然他就會拿出僅有的那一張≌沟伲現(xiàn)在,那個黑衣人往盒子里面放了一張紙條苔咪,那他就會拿出那張紙條锰悼,然后觀察小明是否走完了那段10米長的路。當(dāng)他看到小明走完那段路团赏,在終點空閑下來了之后箕般,他就會馬上把紙條遞給小明,小明就會按照紙條上的“任務(wù)”項去做事舔清,大聲喊出了“旺旺”丝里。
在這件事中曲初,黑衣人和管理盒子的人跟小明是互不干涉,只有交流杯聚。也就是說臼婆,小明走小明的路,那兩個人做他們自己的事械媒,井水不犯河水目锭,歲月靜好。當(dāng)然纷捞,那兩個人也不屬于“道路管理協(xié)會”的管理中,而小明是受到監(jiān)管的被去。

延伸1

function fn() {
  setTimeout( () => {
    console.log( 'hahaha' )
  }, 1000)

  console.log( 'fn' )

  while( true ) {}
}

在這段代碼中主儡,setTimeout 調(diào)用返回之后,到 console.log( 'fn' ) 輸出 'fn'惨缆,往下執(zhí)行到一個 while( true ) 的循環(huán)糜值。此時主線程陷入死循環(huán),沒有空閑的時間坯墨,即使 timer_handler 監(jiān)控到 timer 觸發(fā)寂汇,推入 queue ,也無法執(zhí)行捣染,因為主線程一直繁忙骄瓣。所以 'hahaha' 一直都不會輸出。

延伸2

在 MDN 中學(xué)習(xí) async/await 的時候耍攘,看到了一個例子

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function add1(x) {
  var a = resolveAfter2Seconds(20);
  var b = resolveAfter2Seconds(30);
  return x + await a + await b;
}

add1(10).then(v => {
  console.log(v);  // prints 60 after 2 seconds.
});

async function add2(x) {
  var a = await resolveAfter2Seconds(20);
  var b = await resolveAfter2Seconds(30);
  return x + a + b;
}

add2(10).then(v => {
  console.log(v);  // prints 60 after 4 seconds.
});

突然對 add1函數(shù) 2秒輸出和 add2函數(shù) 4秒輸出產(chǎn)生了疑問榕栏,于是自己仔細(xì)琢磨了一下,認(rèn)為:
首先蕾各,async/await函數(shù) 可以看做是 generator函數(shù) 的一個進(jìn)化版扒磁,只不過返回的是一個 promise,await 有暫停函數(shù)的功能式曲,也相當(dāng)于一個執(zhí)行器妨托,看做自動執(zhí)行 then 或者 catch,自動拿出 promise 中 resolve 或者 reject 的值吝羞。

(未完待續(xù)...)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兰伤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子脆贵,更是在濱河造成了極大的恐慌医清,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卖氨,死亡現(xiàn)場離奇詭異会烙,居然都是意外死亡负懦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門柏腻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纸厉,“玉大人,你說我怎么就攤上這事五嫂】牌罚” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵沃缘,是天一觀的道長躯枢。 經(jīng)常有香客問我,道長槐臀,這世上最難降的妖魔是什么锄蹂? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮水慨,結(jié)果婚禮上得糜,老公的妹妹穿的比我還像新娘。我一直安慰自己晰洒,他們只是感情好朝抖,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谍珊,像睡著了一般治宣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抬驴,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天炼七,我揣著相機(jī)與錄音,去河邊找鬼布持。 笑死豌拙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的题暖。 我是一名探鬼主播按傅,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼胧卤!你這毒婦竟也來了唯绍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤枝誊,失蹤者是張志新(化名)和其女友劉穎况芒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叶撒,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡绝骚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年耐版,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片压汪。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡粪牲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出止剖,到底是詐尸還是另有隱情腺阳,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布穿香,位于F島的核電站亭引,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扔水。R本人自食惡果不足惜痛侍,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望魔市。 院中可真熱鬧,春花似錦赵哲、人聲如沸待德。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽将宪。三九已至,卻和暖如春橡庞,著一層夾襖步出監(jiān)牢的瞬間较坛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工扒最, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留丑勤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓吧趣,卻偏偏與公主長得像法竞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子强挫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

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

  • 名詞解釋 "event-loop": 事件循環(huán)"non-blocking": 非堵塞"callback": 回調(diào)函...
    coolheadedY閱讀 1,383評論 0 3
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,182評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理岔霸,服務(wù)發(fā)現(xiàn),斷路器俯渤,智...
    卡卡羅2017閱讀 134,665評論 18 139
  • 我個人感覺這次題目質(zhì)量是可以的呆细,很模擬現(xiàn)實滲透場景,從外網(wǎng)到內(nèi)網(wǎng)到域控八匠,到達(dá)一定階段給個flag但是也有吐槽的點絮爷,...
    _阿燁_閱讀 743評論 0 2
  • 很多年前趴酣,我喜歡向別人承諾永遠(yuǎn)。 因為那個時候我覺得略水,永遠(yuǎn)不是一個時間概念价卤,而是一種表達(dá)我情感熾熱的修飾。而那些曾...
    離合不騷閱讀 210評論 0 0