面試之萬能答案:事件循環

面試時你是否會遇到下面的問題?web

  1. setTimeout爲何沒有按寫好的延遲時間執行?
  2. setTimeout的實現原理是什麼?
  3. 你知道Event Loop嗎?
  4. 你能說下js的異步執行機制嗎?
  5. 你能說下宏任務和微任務嗎?
  6. 你能寫出下面的代碼的最終的執行結果嗎?
console.log(1)
setTimeout(function(){
	console.log(2)
},1000)
new Promise(function(resolve,rejected){
	console.log(3)
	resolve('同步任務結果')
}).then(res=>{
	console.log('4',res)
})
console.log(5)
複製代碼

其實這些問題的核心都是一個:事件循環,那事件循環究竟是什麼呢?面試

那下面就讓咱們來一塊兒學起來吧💃💃ajax

1、事件循環

你們都知道js是個單線程,單線程就意味着任務要排隊,那麼像setTimeout,ajax這種任務,若是也要排隊,就會形成等待耗時,長時間等待若是無響應,就會形成頁面空白,用戶體驗差。因此就出現了同步異步任務,同步任務在主線程上執行,異步任務在旁邊執行,執行後把結果放在任務隊列中,等到同步任務全都執行完畢後,就去任務隊列中取異步任務的結果,而後按順序執行。異步任務隊列中還分爲微任務和宏任務,微任務先與宏任務執行。如此反覆的一個過程就叫事件循環,也就是js的異步執行機制promise

下面來逐條分析瀏覽器

1.首先js是單線程,同一時間只能作一件事情

爲何是單線程呢?由於js多用來處理dom操做,若是是多線程,2個線程分別對dom作操做, 瀏覽器不能知道以哪一個線程爲主,因此js必須是單線程bash

2. 單線程也就意味着全部的任務是要排隊的

瀏覽器雖然是多線程的,可是隻分配了一個主線程給js執行任務,並且一次只能執行一個任務, 可是js中不少的好比
網絡請求(http請求的鏈接須要tcp三次握手創建鏈接,有等待時間)
定時器(延時操做)
事件監聽(onclick事件)
等會很是的耗時,致使執行效率低下,甚至頁面假死網絡

3. 因此瀏覽器爲異步的任務開啓了另外的線程,那個異步任務的線程完成任務後,主線程是怎麼知道的呢?(異步叫任務隊列)

整個程序是事件驅動的,每一個事件都會綁定相應的回調函數,任務隊列中都是已經完成的異步操做, 而不是你註冊的異步事件 注意:不管異步操做什麼時候開始執行,只要執行完畢就放在消息隊列中,也就是說放在隊列中的是回調函數的結果多線程

4. 任務隊列又分爲

1.宏任務:

  • script腳本(總體代碼)、
  • setTimeout、setInterval、
  • I/O(點擊按鈕之類的交互)、
  • postMessage、
  • MessageChannel:容許咱們建立一個新的消息通道
  • setImmediate(Node.js環境):把一些須要長時間運行的操做放在一個回調函數裏,在瀏覽器完成後面的其餘語句後, 就當即執行這個回調函數

Vue 中對於宏任務的實現:dom

  • 優先檢測是否支持原生的setImmediate(這是一個高版本IE和Edge才支持的特性)
  • 再去檢測是否原生支持MessageChannel
  • 以上都不支持, 就降級爲setTimeout 0

2.微任務:

  • ES6 的Promise:鏈式操做,解決js的地獄回調問題
  • MutaionObserver:能夠監聽dom結構變化的接口
  • process.nextTick(Node.js環境):定義一個動做,而且讓這個動做在下一個事件輪詢的時間點上執行

他們的執行順序是:先執行全部的微任務,再執行下一個宏任務 異步

全部進入異步的都是事件回調的那部分代碼,因此

new promise實例化過程當中的代碼是同步的,then以後執行的纔是微任務,微任務會在宏任務以前執行

await 是基於promise封裝的,因此await以前的是同步代碼,以後的是異步

5. 因此js異步的執行機制就是事件循環機制

  1. 全部的任務都在主線程執行, 造成一個執行棧
  2. 主線程以外存在一個任務隊列(task queue),只要異步任務有了運行結果, 就會放置在任務隊列中等待
  3. 當同步任務所有完成後就會去任務隊列中讀取異步任務,進入執行棧執行
  4. 主線程不斷的重複上面三個步驟,因此叫事件循環機制

6.答案

看完上面應該就知道該如何回答面試官的問題了吧!

  • 1:由於事件循環是先執行同步任務,再執行異步任務,而setTimeout屬於異步任務,若是延時時間是1秒,可是同步任務太多,都完成須要3秒,那麼天然就沒有按照延遲的時間1秒執行了。
  • 2:setTimeout的實現原理就是事件循環:將setTimeout的處理結果放在異步任務隊列中,等到執行棧中的任務完成後,按放入順序去隊伍隊列讀取任務
  • 三、4:都是一個答案,就是上面總結的那一段,簡單說就是其實就是主線程不斷的去任務隊列讀取事件,就叫事件循環機制
  • 5:異步任務分爲宏任務和微任務,當要執行一個宏任務時,先去看看微任務隊列中是否有微任務,而後先執行完全部的微任務,再去執行宏任務
  • 6:1 3 5 4 2

1 3 5 是同步任務。按順序執行。
4 是微任務。
2 是宏任務。

7.看個著名的圖片鞏固一下

2.png
在上圖中,調用棧中遇到DOM操做、ajax請求以及setTimeout等WebAPIs的時候就會交給瀏覽器內核的其餘模塊進行處理,webkit內核在Javasctipt執行引擎以外,有一個重要的模塊是webcore模塊。.對於圖中WebAPIs提到的三種API,webcore分別提供了DOM Binding、network、timer模塊來處理底層實現。等到這些模塊處理完這些操做的時候將回調函數放入任務隊列中,以後等棧中的task執行完以後再去執行任務隊列之中的回調函數。

2、setTimeout的應用

既然已經說到這裏了, 順便說下有關於setTimeout的應用的面試題

1. 你知道函數的防抖節流嗎?能夠寫一下實現代碼嗎?

  • 防抖:就是延時操做
  • 應用場景:input框搜索,屢次輸入操做最終做爲一次操做
<!--簡單版本-->
var timer ;
function debounce(){
    if(timer){
        clearTimeout(timer)
    }
    timer = setTimeout(()=>{
        console.log("一秒後發請求")
    },1000)
}
debounce();
複製代碼
  • 節流:固定時間內只執行一次
  • 應用場景:btn提交,屢次點擊只提交一次,須要一個標識,爲false時不提交。
<!--簡單版本-->
var flag = true;
function throttle() {
    if (!flag) return false;
    flag = false;
    setTimeout(() => {
        console.log('2秒內如論調用多少次此函數,都只會打印一次結果')
        flag = true;
    }, 2000)
}
throttle();
複製代碼

傳參數版

2. 能寫一下SetTimeout 實現setInterval嗎?

setTimout 延遲操做,setInterval是按指定週期調用函數 因此核心應該就是,

  • 遞歸調用setTimeout
  • 有個timeid是全局變量
  • 加入清除功能的思路是這樣的:給setTimeout一個id,而後用clearTimeout(id)清除,

若是timeid寫在了函數裏,那麼你去控制檯調用一下代碼會發現,最終return的timeid是210 ,可是每次遞歸調用的setTimeout的id卻都是不同的,然而最後return出來的並非最新的id,那麼你去清除210的時候就發現程序依然在跑。因此timeid須要是全局變量

//簡單版本
let timeid;
function myInterval(fn,time){
    function inner(){
        fn();
        timeid = setTimeout(inner,time)
    }
    inner();
    return timeid;
}
myInterval(()=>{console.log('重複調用')},1000)
setTimeout(()=>{
    clearTimeout (timeid)
},6000)

複製代碼
相關文章
相關標籤/搜索