一道Javascript面試題引發(fā)的血案

文章首發(fā)于 szhshp的第三邊境研究所 ,轉(zhuǎn)載請(qǐng)注明

一道Javascript面試題引發(fā)的血案

先來(lái)看幾道面試題竖瘾,公司的開(kāi)發(fā)們都嘗試做了一下,然而基本沒(méi)有人能夠全部答對(duì)。

覆蓋的考點(diǎn)很多卿嘲,也有一些難度,題目挺有意思建議手動(dòng)執(zhí)行一邊玩玩夫壁。

Question 1

    for (var i = 0; i <5 ; i++) {
        setTimeout(function(){
            console.log(i)
        ),1000}
    }
    console.log(i)
  • Q:這道題目會(huì)輸出什么拾枣?
  • A:這道題目還比較簡(jiǎn)單,如果對(duì)Javascript稍微有一點(diǎn)深入的同學(xué)都會(huì)發(fā)現(xiàn)這道題目循環(huán)里面出現(xiàn)了閉包,因此輸出的數(shù)字是完全相同的梅肤,最后的輸出也是完全相同的司蔬。
  • 考點(diǎn):閉包,(偽)異步

Question 2

    for (let i = 0; i <5 ; i++) { //注意var變成了let
        setTimeout(function(){
            console.log(i)
        },1000)
    }
    console.log(i)
  • Q:這道題目會(huì)輸出什么姨蝴?

  • A:這道題目其實(shí)是個(gè)坑俊啼。首先題目與Q1的區(qū)別就是變量i的定義改為了關(guān)鍵字let,使用let的時(shí)候會(huì)將變量限制在循環(huán)之中左医,因此第二個(gè)輸出其實(shí)會(huì)報(bào)錯(cuò)授帕。另外setTimeout實(shí)現(xiàn)了(偽)異步,同時(shí)因?yàn)閘et將變量作用域進(jìn)行了控制浮梢,破壞了閉包結(jié)構(gòu)跛十,因此會(huì)按照正常順序輸出。

    關(guān)于let關(guān)鍵字[1]

    Use the let statement to declare a variable, the scope of which is restricted to the block in which it is declared. You can assign values to the variables when you declare them or later in your script.
    A variable declared using let cannot be used before its declaration or an error will result..

  • 考點(diǎn):閉包秕硝,(偽)異步芥映,作用域

Question 3

同樣是Q1的代碼

    for (var i = 0; i <5 ; i++) {  //DO NOT MODIFY
        setTimeout(function(){ //DO NOT MODIFY
            console.log(i) 
        },1000)
    }
    console.log(i)  //DO NOT MODIFY
  • Q:修改上述代碼(部分行不允許修改,可以在代碼間插入)远豺,以實(shí)現(xiàn)“每隔一秒輸出一個(gè)數(shù)字并且順序?yàn)?-5”

  • A

    1. 首先考到了破壞閉包結(jié)構(gòu)奈偏,破壞閉包的方法很多,最簡(jiǎn)單的是將跨域變量轉(zhuǎn)換成范圍內(nèi)的變量
    2. 其次考到了setTimeout事件隊(duì)列的處理
    for (var i = 0; i <5 ; i++) {
        (function(i){
            setTimeout(function(){
                console.log(i)
            },1000*i)    
        })(i)           //將i作為參數(shù)傳入匿名函數(shù)躯护,如此破壞了閉包內(nèi)跨域訪問(wèn)
    }
    setTimeout(function (){
      console.log(i);
    }, 5000);               //強(qiáng)行將5放到5sec后輸出
    
  • 考點(diǎn):閉包惊来,(偽)異步,作用域榛做,事件隊(duì)列

Question 4

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

//Ouput for a long time
for (var i = 0; i < 1000; i++) {
    console.log('');
};

console.log(1)

window.setTimeout(function (){
    console.log(3)
},0);
  • Q:這道題目會(huì)輸出什么唁盏?
  • A:可能有些同學(xué)會(huì)記得,setTimeout是一個(gè)回調(diào)函數(shù)检眯,因此無(wú)論延時(shí)多少結(jié)果都是最后輸出厘擂。
  • 考點(diǎn):(偽)異步,事件隊(duì)列

Question 5

這道題目其實(shí)是其他地方抄襲來(lái)的[2]锰瘸,正好和之前考點(diǎn)有一定重疊因此一起放了過(guò)來(lái):

    setTimeout(function(){console.log(4)},0);
    new Promise(function(resolve){
        console.log(1)

        //time consuming ops
        for( var i=0 ; i<10000 ; i++ ){
            i==9999 && resolve();
        }

        console.log(2)
    }).then(function(){
        console.log(5)
    });
    console.log(3);
  • Q:這道題目會(huì)輸出什么刽严?

  • A:輸出是12354

    關(guān)于這個(gè)輸出,有如下幾個(gè)邏輯:

    1. 4是setTimeOut.callback的輸出避凝,加入MacroTask末端舞萄,
    2. 輸出1
    3. 執(zhí)行Promise.resolve()將輸出5的callback放到MicroTask中(注意這里不是MacroTask
    4. 輸出2
    5. 輸出3
    6. MacroTask首個(gè)任務(wù)執(zhí)行完畢
    7. 查找MicroTask里面有沒(méi)有任務(wù),發(fā)現(xiàn)有管削,執(zhí)行倒脓,輸出5
    8. 查找MacroTask里面有沒(méi)有任務(wù),發(fā)現(xiàn)有含思,執(zhí)行崎弃,輸出4
    9. 查找MicroTask里面有沒(méi)有任務(wù)甘晤,發(fā)現(xiàn)沒(méi)有,可以休息了
    10. 查找MacroTask里面有沒(méi)有任務(wù)饲做,發(fā)現(xiàn)沒(méi)有线婚,可以睡覺(jué)了
    11. 執(zhí)行完畢

關(guān)于事件循環(huán)/關(guān)于macrotaskmicrotask[3]

簡(jiǎn)介

一個(gè)事件循環(huán)(EventLoop)中會(huì)有一個(gè)正在執(zhí)行的任務(wù)(Task),而這個(gè)任務(wù)就是從 macrotask 隊(duì)列中來(lái)的盆均。在whatwg規(guī)范中有 queue 就是任務(wù)隊(duì)列塞弊。當(dāng)這個(gè) macrotask 執(zhí)行結(jié)束后所有可用的 microtask 將會(huì)在同一個(gè)事件循環(huán)中執(zhí)行,當(dāng)這些 microtask 執(zhí)行結(jié)束后還能繼續(xù)添加 microtask 一直到真?zhèn)€ microtask 隊(duì)列執(zhí)行結(jié)束泪姨。

怎么用

基本來(lái)說(shuō)游沿,當(dāng)我們想以同步的方式來(lái)處理異步任務(wù)時(shí)候就用 microtask(比如我們需要直接在某段代碼后就去執(zhí)行某個(gè)任務(wù),就像Promise一樣)驴娃。

其他情況就直接用 macrotask奏候。

兩者的具體實(shí)現(xiàn)

  • macrotasks: setTimeout setInterval setImmediate I/O UI渲染
  • microtasks: Promise process.nextTick Object.observe MutationObserver

從規(guī)范中理解

規(guī)范:https://html.spec.whatwg.org/multipage/webappapis.html#task-queue

  • 一個(gè)事件循環(huán)(event loop)會(huì)有一個(gè)或多個(gè)任務(wù)隊(duì)列(task queue) task queue 就是 macrotask queue
  • 每一個(gè) event loop 都有一個(gè) microtask queue
  • task queue == macrotask queue != microtask queue
  • 一個(gè)任務(wù) task 可以放入 macrotask queue 也可以放入 microtask queue 中
  • 當(dāng)一個(gè) task 被放入隊(duì)列 queue(macro或micro) 那這個(gè) task 就可以被立即執(zhí)行了

再來(lái)回顧下事件循環(huán)如何執(zhí)行一個(gè)任務(wù)的流程

當(dāng)執(zhí)行棧(call stack)為空的時(shí)候循集,開(kāi)始依次執(zhí)行:

  1. 把最早的任務(wù)(task A)放入任務(wù)隊(duì)列
  2. 如果 task A 為null (那任務(wù)隊(duì)列就是空)唇敞,直接跳到第6步
  3. 將 currently running task 設(shè)置為 task A
  4. 執(zhí)行 task A (也就是執(zhí)行回調(diào)函數(shù))
  5. 將 currently running task 設(shè)置為 null 并移出 task A
  6. 執(zhí)行 microtask 隊(duì)列
    1. 在 microtask 中選出最早的任務(wù) task X
    2. 如果 task X 為null (那 microtask 隊(duì)列就是空),直接跳到 g
    3. 將 currently running task 設(shè)置為 task X
    4. 執(zhí)行 task X
    5. 將 currently running task 設(shè)置為 null 并移出 task X
    6. 在 microtask 中選出最早的任務(wù) , 跳到 b
    7. 結(jié)束 microtask 隊(duì)列
  7. 跳到第一步

上面就算是一個(gè)簡(jiǎn)單的 event-loop 執(zhí)行模型

再簡(jiǎn)單點(diǎn)可以總結(jié)為:

  1. 在 macrotask 隊(duì)列中執(zhí)行最早的那個(gè) task 咒彤,然后移出
  2. 執(zhí)行 microtask 隊(duì)列中所有可用的任務(wù)疆柔,然后移出
  3. 下一個(gè)循環(huán),執(zhí)行下一個(gè) macrotask 中的任務(wù) (再跳到第2步)

其他

  1. 當(dāng)一個(gè)task(在 macrotask 隊(duì)列中)正處于執(zhí)行狀態(tài)镶柱,也可能會(huì)有新的事件被注冊(cè)旷档,那就會(huì)有新的 task 被創(chuàng)建。比如下面兩個(gè)
    1. promiseA.then() 的回調(diào)就是一個(gè) task
    1. promiseA 是 resolved或rejected: 那這個(gè) task 就會(huì)放入當(dāng)前事件循環(huán)回合的 microtask queue
    1. promiseA 是 pending: 這個(gè) task 就會(huì)放入 事件循環(huán)的未來(lái)的某個(gè)(可能下一個(gè))回合的 microtask queue 中
    1. setTimeout 的回調(diào)也是個(gè) task 歇拆,它會(huì)被放入 macrotask queue 即使是 0ms 的情況
  2. microtask queue 中的 task 會(huì)在事件循環(huán)的當(dāng)前回合中執(zhí)行鞋屈,因此 macrotask queue 中的 task 就只能等到事件循環(huán)的下一個(gè)回合中執(zhí)行了
  3. click ajax setTimeout 的回調(diào)是都是 task, 同時(shí),包裹在一個(gè) script 標(biāo)簽中的js代碼也是一個(gè) task 確切說(shuō)是 macrotask故觅。

參考文獻(xiàn)


  1. let 語(yǔ)句 (JavaScript) ?

  2. https://www.zhihu.com/question/36972010 ?

  3. https://github.com/ccforward/cc/issues/48 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末厂庇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子输吏,更是在濱河造成了極大的恐慌权旷,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苛预,死亡現(xiàn)場(chǎng)離奇詭異俺亮,居然都是意外死亡该窗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)译柏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人姐霍,你說(shuō)我怎么就攤上這事鄙麦。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵黔衡,是天一觀的道長(zhǎng)蚓聘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)盟劫,這世上最難降的妖魔是什么夜牡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮侣签,結(jié)果婚禮上塘装,老公的妹妹穿的比我還像新娘。我一直安慰自己影所,他們只是感情好蹦肴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著猴娩,像睡著了一般阴幌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卷中,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天矛双,我揣著相機(jī)與錄音,去河邊找鬼蟆豫。 笑死议忽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的十减。 我是一名探鬼主播栈幸,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼帮辟!你這毒婦竟也來(lái)了速址?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤织阅,失蹤者是張志新(化名)和其女友劉穎壳繁,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體荔棉,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡闹炉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了润樱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渣触。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖壹若,靈堂內(nèi)的尸體忽然破棺而出嗅钻,到底是詐尸還是另有隱情皂冰,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布养篓,位于F島的核電站秃流,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏柳弄。R本人自食惡果不足惜舶胀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碧注。 院中可真熱鬧嚣伐,春花似錦、人聲如沸萍丐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)逝变。三九已至基茵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骨田,已是汗流浹背耿导。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工声怔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留态贤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓醋火,卻偏偏與公主長(zhǎng)得像悠汽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芥驳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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