javascript
笔试题——JavaScript事件循环机制(event loop、macrotask、microtask)
今天做了一道筆試題覺(jué)得很有意義分享給大家,題目如下:
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');復(fù)制代碼JavaScript 都知道它是一門(mén)單線程的語(yǔ)言,這也就意味著 JS 無(wú)法進(jìn)行多線程編程,但是 JS 當(dāng)中卻有著無(wú)處不在的異步概念 。要完全理解異步,就需要了解 JS 的運(yùn)行核心——事件循環(huán)(event loop)。
?一、什么是事件隊(duì)列?
首先來(lái)看一個(gè)小小的demo
console.log('start'); setTimeout(()=>{console.log('A'); },1000); console.log('end'); //start //end //A復(fù)制代碼js執(zhí)行之后,程序輸出 'start' 和 'end',在大約1s之后輸出了 'A' 。那我們就有疑問(wèn)了?為什么A不在end之前執(zhí)行呢?
這是因?yàn)?setTimeout 是一個(gè)異步的函數(shù)。意思也就是說(shuō)當(dāng)我們?cè)O(shè)置一個(gè)延遲函數(shù)的時(shí)候,當(dāng)前腳本并不會(huì)阻塞,它只是會(huì)在瀏覽器的事件表中進(jìn)行記錄,程序會(huì)繼續(xù)向下執(zhí)行。當(dāng)延遲的時(shí)間結(jié)束之后,事件表會(huì)將回調(diào)函數(shù)添加至事件隊(duì)列(task queue)中,事件隊(duì)列拿到了任務(wù)過(guò)后便將任務(wù)壓入執(zhí)行棧(stack)當(dāng)中,執(zhí)行棧執(zhí)行任務(wù),輸出 'A'。
事件隊(duì)列是一個(gè)存儲(chǔ)著待執(zhí)行任務(wù)的隊(duì)列,其中的任務(wù)嚴(yán)格按照時(shí)間先后順序執(zhí)行,排在隊(duì)頭的任務(wù)將會(huì)率先執(zhí)行,而排在隊(duì)尾的任務(wù)會(huì)最后執(zhí)行。事件隊(duì)列每次僅執(zhí)行一個(gè)任務(wù),在該任務(wù)執(zhí)行完畢之后,再執(zhí)行下一個(gè)任務(wù)。執(zhí)行棧則是一個(gè)類(lèi)似于函數(shù)調(diào)用棧的運(yùn)行容器,當(dāng)執(zhí)行棧為空時(shí),JS 引擎便檢查事件隊(duì)列,如果不為空的話,事件隊(duì)列便將第一個(gè)任務(wù)壓入執(zhí)行棧中運(yùn)行。
那么我將這個(gè)例子做一個(gè)小小的改動(dòng)看一看:
console.log('start'); setTimeout(()=>{console.log('A'); },0); console.log('end'); //start //end //A復(fù)制代碼可以看出,我們將settimeout第二個(gè)參數(shù)設(shè)置為0后,'A' 也總是會(huì)在 'end' 之后輸出。所以究竟發(fā)生了什么?這是因?yàn)?setTimeout 的回調(diào)函數(shù)只是會(huì)被添加至事件隊(duì)列,而不是立即執(zhí)行。由于當(dāng)前的任務(wù)沒(méi)有執(zhí)行結(jié)束,所以 setTimeout 任務(wù)不會(huì)執(zhí)行,直到輸出了 'end' 之后,當(dāng)前任務(wù)執(zhí)行完畢,執(zhí)行棧為空,這時(shí)事件隊(duì)列才會(huì)把 setTimeout 回調(diào)函數(shù)壓入執(zhí)行棧執(zhí)行。
?二、Promise的含義和基本用法?
所謂Promise,簡(jiǎn)單說(shuō)就是一個(gè)容器,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。從語(yǔ)法上說(shuō),Promise 是一個(gè)對(duì)象,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進(jìn)行處理。
寫(xiě)一個(gè)小demo看一下Promise的運(yùn)行機(jī)制:
let promise = new Promise(function(resolve, reject) {console.log('Promise');resolve(); });promise.then(function() {console.log('resolved.'); });console.log('Hi!'); // Promise // Hi! // resolved復(fù)制代碼上面代碼中,Promise 新建后立即執(zhí)行,所以首先輸出的是Promise。然后,then方法指定的回調(diào)函數(shù),將在當(dāng)前腳本所有同步任務(wù)執(zhí)行完才會(huì)執(zhí)行,所以resolved最后輸出。
?三、Macrotasks和Microtasks
Macrotasks和Microtasks 都屬于上述的異步任務(wù)中的一種,他們分別有如下API:
macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks: process.nextTick, Promise, MutationObserver
setTimeout的macrotask, 和 Promise的microtask 有哪些不同,先來(lái)看下代碼如下:
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復(fù)制代碼如上代碼可以看到,Promise的函數(shù)代碼的異步任務(wù)會(huì)優(yōu)先于setTimeout的延時(shí)為0的任務(wù)先執(zhí)行。
原因是任務(wù)隊(duì)列分為 macrotasks 和 microtasks, 而promise中的then方法的函數(shù)會(huì)被推入到microtasks隊(duì)列中,而setTimeout函數(shù)會(huì)被推入到macrotasks
任務(wù)隊(duì)列中,在每一次事件循環(huán)中,macrotask只會(huì)提取一個(gè)執(zhí)行,而microtask會(huì)一直提取,直到microsoft隊(duì)列為空為止。
也就是說(shuō)如果某個(gè)microtask任務(wù)被推入到執(zhí)行中,那么當(dāng)主線程任務(wù)執(zhí)行完成后,會(huì)循環(huán)調(diào)用該隊(duì)列任務(wù)中的下一個(gè)任務(wù)來(lái)執(zhí)行,直到該任務(wù)隊(duì)列到最后一個(gè)任務(wù)為止。
而事件循環(huán)每次只會(huì)入棧一個(gè)macrotask,主線程執(zhí)行完成該任務(wù)后又會(huì)檢查microtasks隊(duì)列并完成里面的所有任務(wù)后再執(zhí)行macrotask的任務(wù)。
?四、分析本題目
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');復(fù)制代碼1、首先?setTimeout?A 被加入到事件隊(duì)列中? ==>? 此時(shí)macrotasks中有[‘A’];
2、obj.func()執(zhí)行時(shí),setTimeout B 被加入到事件隊(duì)列中? ==> 此時(shí)macrotasks中有[‘A’,‘B’];
3、接著return一個(gè)Promise對(duì)象,Promise 新建后立即執(zhí)行 執(zhí)行console.log('C'); 控制臺(tái)首次打印‘C’;
4、然后,then方法指定的回調(diào)函數(shù),被加入到microtasks隊(duì)列,將在當(dāng)前腳本所有同步任務(wù)執(zhí)行完才會(huì)執(zhí)行。 ==> 此時(shí)microtasks中有[‘D’];
5、然后繼續(xù)執(zhí)行當(dāng)前腳本的同步任務(wù),故控制臺(tái)第二次輸出‘E’;
6、此時(shí)所有同步任務(wù)執(zhí)行完畢,如上所述先檢查microtasks隊(duì)列,完成其中所有任務(wù),故控制臺(tái)第三次輸出‘D’;
7、最后再執(zhí)行macrotask的任務(wù),并且按照入隊(duì)列的時(shí)間順序,控制臺(tái)第四次輸出‘A’,控制臺(tái)第五次輸出‘B’。
?五、執(zhí)行js代碼
分析與實(shí)際符合,NICE!
參考文章:www.cnblogs.com/tugenhua070…
還有阮老師的promise介紹:es6.ruanyifeng.com/?search=pro…
文章本人原創(chuàng),轉(zhuǎn)載請(qǐng)?jiān)u論;
前端菜鳥(niǎo)對(duì)JavaScript的理解還有很多不足,如有錯(cuò)誤歡迎大家指出來(lái);
喜歡的點(diǎn)個(gè)贊把!
總結(jié)
以上是生活随笔為你收集整理的笔试题——JavaScript事件循环机制(event loop、macrotask、microtask)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: rocketmq之producer解析
- 下一篇: 05用线程类Thread开启线程