一道Javascript面試題引發的血案

來源:互聯網
上載者:User

標籤:git   llb   開發   console   非同步任務   red   java   strong   blank   

文章首發於 szhshp的第三邊境研究所 ,轉載請註明

先來看幾道面試題,公司的開發們都嘗試做了一下,然而基本沒有人能夠全部答對。

覆蓋的考點很多,也有一些難度,題目挺有意思建議手動執行一邊玩玩。

Question 1
    for (var i = 0; i <5 ; i++) {        setTimeout(function(){            console.log(i)        ),1000}    }    console.log(i)
  • Q:這道題目會輸出什嗎?
  • A:這道題目還比較簡單,如果對Javascript稍微有一點深入的同學都會發現這道題目迴圈裡面出現了閉包,因此輸出的數字是完全相同的,最後的輸出也是完全相同的。
  • 考點:閉包,(偽)非同步
Question 2
    for (let i = 0; i <5 ; i++) { //注意var變成了let        setTimeout(function(){            console.log(i)        },1000)    }    console.log(i)
  • Q:這道題目會輸出什嗎?
  • A:這道題目其實是個坑。首先題目與Q1的區別就是變數i的定義改為了關鍵字let,使用let的時候會將變數限制在迴圈之中,因此第二個輸出其實會報錯。另外setTimeout實現了(偽)非同步,同時因為let將變數範圍進行了控制,破壞了閉包結構,因此會按照正常順序輸出。

    關於let關鍵字[^3]

    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..

  • 考點:閉包,(偽)非同步,範圍

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:修改上述代碼(部分行不允許修改,可以在代碼間插入),以實現“每隔一秒輸出一個數字並且順序為0-5”
  • A

    1. 首先考到了破壞閉包結構,破壞閉包的方法很多,最簡單的是將跨域變數轉換成範圍內的變數
    2. 其次考到了setTimeout事件隊列的處理

      for (var i = 0; i <5 ; i++) { (function(i){     setTimeout(function(){         console.log(i)     },1000*i)     })(i)           //將i作為參數傳入匿名函數,如此破壞了閉包內跨域訪問}setTimeout(function (){console.log(i);}, 5000);               //強行將5放到5sec後輸出
  • 考點:閉包,(偽)非同步,範圍,事件隊列

    Question 4
window.setTimeout(function (){    console.log(2)},1);//Ouput for a long timefor (var i = 0; i < 1000; i++) {    console.log(‘‘);};console.log(1)window.setTimeout(function (){    console.log(3)},0);
  • Q:這道題目會輸出什嗎?
  • A:可能有些同學會記得,setTimeout是一個回呼函數,因此無論延時多少結果都是最後輸出。
  • 考點:(偽)非同步,事件隊列
Question 5

這道題目其實是其他地方抄襲來的[^2],正好和之前考點有一定重疊因此一起放了過來:

    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:這道題目會輸出什嗎?
  • A:輸出是12354

    關於這個輸出,有如下幾個邏輯:

    1. 4是setTimeOut.callback的輸出,加入MacroTask末端,
    2. 輸出1
    3. 執行Promise.resolve()將輸出5的callback放到MicroTask中(注意這裡不是MacroTask)
    4. 輸出2
    5. 輸出3
    6. MacroTask首個任務執行完畢
    7. 尋找MicroTask裡面有沒有任務,發現有,執行,輸出5
    8. 尋找MacroTask裡面有沒有任務,發現有,執行,輸出4
    9. 尋找MicroTask裡面有沒有任務,發現沒有,可以休息了
    10. 尋找MacroTask裡面有沒有任務,發現沒有,可以睡覺了
    11. 執行完畢
關於事件迴圈/關於macrotask和microtask[^1]簡介

一個事件迴圈(EventLoop)中會有一個正在執行的任務(Task),而這個任務就是從 macrotask 隊列中來的。在whatwg規範中有 queue 就是任務隊列。當這個 macrotask 執行結束後所有可用的 microtask 將會在同一個事件迴圈中執行,當這些 microtask 執行結束後還能繼續添加 microtask 一直到真箇 microtask 隊列執行結束。

怎麼用

基本來說,當我們想以同步的方式來處理非同步任務時候就用 microtask(比如我們需要直接在某段代碼後就去執行某個任務,就像Promise一樣)。

其他情況就直接用 macrotask。

兩者的具體實現
  • macrotasks: setTimeout setInterval setImmediate I/O UI渲染
  • microtasks: Promise process.nextTick Object.observe MutationObserver
從規範中理解

規範:https://html.spec.whatwg.org/multipage/webappapis.html#task-queue

  • 一個事件迴圈(event loop)會有一個或多個任務隊列(task queue) task queue 就是 macrotask queue
  • 每一個 event loop 都有一個 microtask queue
  • task queue == macrotask queue != microtask queue
  • 一個任務 task 可以放入 macrotask queue 也可以放入 microtask queue 中
  • 當一個 task 被放入隊列 queue(macro或micro) 那這個 task 就可以被立即執行了

再來回顧下事件迴圈如何執行一個任務的流程

當執行棧(call stack)為空白的時候,開始依次執行:

  1. 把最早的任務(task A)放入任務隊列
  2. 如果 task A 為null (那任務隊列就是空),直接跳到第6步
  3. 將 currently running task 設定為 task A
  4. 執行 task A (也就是執行回呼函數)
  5. 將 currently running task 設定為 null 並移出 task A
  6. 執行 microtask 隊列
    1. 在 microtask 中選出最早的任務 task X
    2. 如果 task X 為null (那 microtask 隊列就是空),直接跳到 g
    3. 將 currently running task 設定為 task X
    4. 執行 task X
    5. 將 currently running task 設定為 null 並移出 task X
    6. 在 microtask 中選出最早的任務 , 跳到 b
    7. 結束 microtask 隊列
  7. 跳到第一步

上面就算是一個簡單的 event-loop 執行模型

再簡單點可以總結為:

  1. 在 macrotask 隊列中執行最早的那個 task ,然後移出
  2. 執行 microtask 隊列中所有可用的任務,然後移出
  3. 下一個迴圈,執行下一個 macrotask 中的任務 (再跳到第2步)
其他
  1. 當一個task(在 macrotask 隊列中)正處於執行狀態,也可能會有新的事件被註冊,那就會有新的 task 被建立。比如下面兩個
    1. promiseA.then() 的回調就是一個 task1. promiseA 是 resolved或rejected: 那這個 task 就會放入當前事件迴圈回合的 microtask queue1. promiseA 是 pending: 這個 task 就會放入 事件迴圈的未來的某個(可能下一個)回合的 microtask queue 中1. setTimeout 的回調也是個 task ,它會被放入 macrotask queue 即使是 0ms 的情況
  2. microtask queue 中的 task 會在事件迴圈的當前回合中執行,因此 macrotask queue 中的 task 就只能等到事件迴圈的下一個回合中執行了
  3. click ajax setTimeout 的回調是都是 task, 同時,包裹在一個 script 標籤中的js代碼也是一個 task 確切說是 macrotask。
參考文獻

[^3]: let 語句 (JavaScript) .aspx)
[^2]: https://www.zhihu.com/question/36972010 
[^1]: https://github.com/ccforward/cc/issues/48

一道Javascript面試題引發的血案

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.