筆試題——JavaScript事件循環機制(event loop、macrotask、microtask)

今天作了一道筆試題以爲頗有意義分享給你們,題目以下:html

setTimeout(()=>{
    console.log('A');
},0);
var obj={
    func:function () {
        setTimeout(function () {
            console.log('B')
        },0);
        return new Promise(function (resolve) {
            console.log('C');
            resolve();
        })
    }
};
obj.func().then(function () {
    console.log('D')
});
console.log('E');複製代碼

JavaScript 都知道它是一門單線程的語言,這也就意味着 JS 沒法進行多線程編程,可是 JS 當中卻有着無處不在的異步概念 。要徹底理解異步,就須要瞭解 JS 的運行核心——事件循環(event loop)。
前端

 1、什麼是事件隊列?

首先來看一個小小的demoes6

console.log('start');
setTimeout(()=>{
    console.log('A');
},1000);
console.log('end');
//start
//end
//A複製代碼

js執行以後,程序輸出 'start' 和 'end',在大約1s以後輸出了 'A' 。那咱們就有疑問了?爲何A不在end以前執行呢?
編程

這是由於 setTimeout 是一個異步的函數。意思也就是說當咱們設置一個延遲函數的時候,當前腳本並不會阻塞,它只是會在瀏覽器的事件表中進行記錄,程序會繼續向下執行。當延遲的時間結束以後,事件表會將回調函數添加至事件隊列(task queue)中,事件隊列拿到了任務事後便將任務壓入執行棧(stack)當中,執行棧執行任務,輸出 'A'。
promise

事件隊列是一個存儲着待執行任務的隊列,其中的任務嚴格按照時間前後順序執行,排在隊頭的任務將會率先執行,而排在隊尾的任務會最後執行。事件隊列每次僅執行一個任務,在該任務執行完畢以後,再執行下一個任務。執行棧則是一個相似於函數調用棧的運行容器,當執行棧爲空時,JS 引擎便檢查事件隊列,若是不爲空的話,事件隊列便將第一個任務壓入執行棧中運行。
瀏覽器

那麼我將這個例子作一個小小的改動看一看:bash

console.log('start');
setTimeout(()=>{
    console.log('A');
},0);
console.log('end');
//start
//end
//A複製代碼

能夠看出,咱們將settimeout第二個參數設置爲0後,'A' 也老是會在 'end' 以後輸出。因此究竟發生了什麼?這是由於 setTimeout 的回調函數只是會被添加至事件隊列,而不是當即執行。因爲當前的任務沒有執行結束,因此 setTimeout 任務不會執行,直到輸出了 'end' 以後,當前任務執行完畢,執行棧爲空,這時事件隊列纔會把 setTimeout 回調函數壓入執行棧執行。
多線程

 2、Promise的含義和基本用法?

所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。
異步

寫一個小demo看一下Promise的運行機制:函數

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');
// Promise
// Hi!
// resolved複製代碼

上面代碼中,Promise 新建後當即執行,因此首先輸出的是Promise。而後,then方法指定的回調函數,將在當前腳本全部同步任務執行完纔會執行,因此resolved最後輸出。

 3、Macrotasks和Microtasks

Macrotasks和Microtasks 都屬於上述的異步任務中的一種,他們分別有以下API:
macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks: process.nextTick, Promise, MutationObserver

setTimeout的macrotask, 和 Promise的microtask 有哪些不一樣,先來看下代碼以下:

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

//1
//3
//4
//2複製代碼

如上代碼能夠看到,Promise的函數代碼的異步任務會優先setTimeout的延時爲0的任務先執行。

緣由是任務隊列分爲 macrotasks 和 microtasks, 而promise中的then方法的函數會被推入到microtasks隊列中,而setTimeout函數會被推入到macrotasks

任務隊列中,在每一次事件循環中,macrotask只會提取一個執行,而microtask一直提取,直到microsoft隊列爲空爲止。

也就是說若是某個microtask任務被推入到執行中,那麼當主線程任務執行完成後,會循環調用該隊列任務中的下一個任務來執行,直到該任務隊列到最後一個任務爲止。

而事件循環每次只會入棧一個macrotask,主線程執行完成該任務後又會檢查microtasks隊列並完成裏面的全部任務後再執行macrotask的任務。

 4、分析本題目

setTimeout(()=>{
    console.log('A');
},0);
var obj={
    func:function () {
        setTimeout(function () {
            console.log('B')
        },0);
        return new Promise(function (resolve) {
            console.log('C');
            resolve();
        })
    }
};
obj.func().then(function () {
    console.log('D')
});
console.log('E');複製代碼

一、首先 setTimeout A 被加入到事件隊列中  ==>  此時macrotasks中有[‘A’];

二、obj.func()執行時,setTimeout B 被加入到事件隊列中  ==> 此時macrotasks中有[‘A’,‘B’];

三、接着return一個Promise對象,Promise 新建後當即執行 執行console.log('C'); 控制檯首次打印‘C’;

四、而後,then方法指定的回調函數,被加入到microtasks隊列,將在當前腳本全部同步任務執行完纔會執行。 ==> 此時microtasks中有[‘D’];

五、而後繼續執行當前腳本的同步任務,故控制檯第二次輸出‘E’;

六、此時全部同步任務執行完畢,如上所述先檢查microtasks隊列完成其中全部任務,故控制檯第三次輸出‘D’;

七、最後再執行macrotask的任務,而且按照入隊列的時間順序,控制檯第四次輸出‘A’,控制檯第五次輸出‘B’。

 5、執行js代碼



分析與實際符合,NICE!

參考文章:www.cnblogs.com/tugenhua070…

還有阮老師的promise介紹:es6.ruanyifeng.com/?search=pro…

文章本人原創,轉載請評論;

前端菜鳥對JavaScript的理解還有不少不足,若有錯誤歡迎你們指出來;

喜歡的點個贊把!

相關文章
相關標籤/搜索