摘要:上一輪循環(huán)中有少數(shù)的會被延遲到這一輪的這一階段執(zhí)行。執(zhí)行,在適當(dāng)?shù)臈l件下會阻塞在這個階段執(zhí)行設(shè)定的回調(diào)。
上次大家跟我吃飽喝足又?jǐn)]了一遍PromiseA+,想必大家肯定滿腦子想的都是西瓜可樂......
什么西瓜可樂!明明是Promise!
呃,清醒一下,今天大家搬個小板凳,聽我說說JS中比較有意思的事件環(huán),在了解事件環(huán)之前呢,我們先來了解幾個基本概念。
棧(Stack)棧是一種遵循后進(jìn)先出(LIFO)的數(shù)據(jù)集合,新添加或待刪除的元素都保存在棧的末尾,稱作棧頂,另一端稱作棧底。在棧里,新元素都靠近棧頂,舊元素都接近棧底
感覺說起來并不是很好理解,我們舉個例子,比如有一個乒乓球盒,我們不停的向球盒中放進(jìn)乒乓球,那么最先放進(jìn)去的乒乓球一定是在最下面,最后放進(jìn)去的一定是在最上面,那么如果我們想要把這些球取出來是不是就必須依次從上到下才能拿出來,這個模型就是后進(jìn)先出,就是我們后進(jìn)入球盒的球反而最先出來。
棧的概念其實在我們js中十分的重要,大家都知道我們js是一個單線程語言,那么他單線程在哪里呢,就在他的主工作線程,也就是我們常說的執(zhí)行上下文,這個執(zhí)行上下文就是棧空間,我們來看一段代碼:
console.log("1"); function a(){ console.log("2"); function b(){ console.log("3") } b() } a()
我們知道函數(shù)執(zhí)行的時候會將這個函數(shù)放入到我們的執(zhí)行上下文中,當(dāng)函數(shù)執(zhí)行完畢之后會彈出執(zhí)行棧,那么根據(jù)這個原理我們就能知道這段代碼的運行過程是
首先我們代碼執(zhí)行的時候會有一個全局上下文,此時代碼運行,全局上下文進(jìn)行執(zhí)行棧,處在棧底的位置
我們遇到console.log("1"),這個函數(shù)在調(diào)用的時候進(jìn)入執(zhí)行棧,當(dāng)這句話執(zhí)行完畢也就是到了下一行的時候我們console這個函數(shù)就會出棧,此時棧中仍然只有全局上下文
接著運行代碼,這里注意的是我們遇到的函數(shù)聲明都不會進(jìn)入執(zhí)行棧,只有當(dāng)我們的函數(shù)被調(diào)用被執(zhí)行的時候才會進(jìn)入,這個原理和我們執(zhí)行棧的名字也就一模一樣,接著我們遇到了a();這句代碼這個時候我們的a函數(shù)就進(jìn)入了執(zhí)行棧,然后進(jìn)入到我們a的函數(shù)內(nèi)部中,此時我們的函數(shù)執(zhí)行棧應(yīng)該是 全局上下文 —— a
接著我運行console.log("2"),執(zhí)行棧變成 全局上下文——a——console,接著我們的console運行完畢,我們執(zhí)行棧恢復(fù)成全局上下文 —— a
接著我們遇到了b();那么b進(jìn)入我們的執(zhí)行棧,全局上下文——a——b,
接著進(jìn)入b函數(shù)的內(nèi)部,執(zhí)行console.log("3")的時候執(zhí)行棧為全局上下文——a——b——console,執(zhí)行完畢之后回復(fù)成全局上下文——a——b
然后我們的b函數(shù)就執(zhí)行完畢,然后就被彈出執(zhí)行棧,那么執(zhí)行棧就變成全局上下文——a
然后我們的a函數(shù)就執(zhí)行完畢,然后就被彈出執(zhí)行棧,那么執(zhí)行棧就變成全局上下文
然后我們的全局上下文會在我們的瀏覽器關(guān)閉的時候出棧
我們的執(zhí)行上下文的執(zhí)行過程就是這樣,是不是清楚了很多~
通過上面的執(zhí)行上下文我們可以發(fā)現(xiàn)幾個特點:
執(zhí)行上下文是單線程
執(zhí)行上下文是同步執(zhí)行代碼
當(dāng)有函數(shù)被調(diào)用的時候,這個函數(shù)會進(jìn)入執(zhí)行上下文
代碼運行會產(chǎn)生一個全局的上下文,只有當(dāng)瀏覽器關(guān)閉才會出棧
隊列(Queue)隊列是一種遵循先進(jìn)先出(FIFO)的數(shù)據(jù)集合,新的條目會被加到隊列的末尾,舊的條目會從隊列的頭部被移出。
這里我們可以看到隊列和棧不同的地方是棧是后進(jìn)先出類似于乒乓球盒,而隊列是先進(jìn)先出,也就是說最先進(jìn)入的會最先出去。
同樣我們舉個例子,隊列就好比是我們排隊過安檢,最先來到的人排在隊伍的首位,后來的人接著排在隊伍的后面,然后安檢員會從隊伍的首端進(jìn)行安檢,檢完一個人就放行一個人,是不是這樣的一個隊伍就是先進(jìn)先出的一個過程。
隊列這里我們就要提到兩個概念,宏任務(wù)(macro task),微任務(wù)(micro task)。
任務(wù)隊列Js的事件執(zhí)行分為宏仁務(wù)和微任務(wù)
宏仁務(wù)主要是由script(全局任務(wù)),setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering
微任務(wù)主要是process.nextTick, Promise.then, Object.observer, MutationObserver.
瀏覽器事件環(huán)js執(zhí)行代碼的過程中如果遇到了上述的任務(wù)代碼之后,會先把這些代碼的回調(diào)放入對應(yīng)的任務(wù)隊列中去,然后繼續(xù)執(zhí)行主線程的代碼知道執(zhí)行上下文中的函數(shù)全部執(zhí)行完畢了之后,會先去微任務(wù)隊列中執(zhí)行相關(guān)的任務(wù),微任務(wù)隊列清空之后,在從宏仁務(wù)隊列中拿出任務(wù)放到執(zhí)行上下文中,然后繼續(xù)循環(huán)。
執(zhí)行代碼,遇到宏仁務(wù)放入宏仁務(wù)隊列,遇到微任務(wù)放入微任務(wù)隊列,執(zhí)行其他函數(shù)的時候放入執(zhí)行上下文
執(zhí)行上下文中全部執(zhí)行完畢后,執(zhí)行微任務(wù)隊列
微任務(wù)隊列執(zhí)行完畢后,再到宏仁務(wù)隊列中取出第一項放入執(zhí)行上下文中執(zhí)行
接著就不停循環(huán)1-3的步驟,這就是瀏覽器環(huán)境中的js事件環(huán)
//學(xué)了上面的事件環(huán) 我們來看一道面試題 setTimeout(function () { console.log(1); }, 0); Promise.resolve(function () { console.log(2); }) new Promise(function (resolve) { console.log(3); }); console.log(4); //上述代碼的輸出結(jié)果是什么???
思考思考思考思考~~~
正確答案是3 4 1,是不是和你想的一樣?我們來看一下代碼的運行流程
// 遇到setTimeout 將setTimeout回調(diào)放入宏仁務(wù)隊列中 setTimeout(function () { console.log(1); }, 0); // 遇到了promise,但是并沒有then方法回調(diào) 所以這句代碼會在執(zhí)行過程中進(jìn)入我們當(dāng)前的執(zhí)行上下文 緊接著就出棧了 Promise.resolve(function () { console.log(2); }) // 遇到了一個 new Promise,不知道大家還記不記得我們上一篇文章中講到Promise有一個原則就是在初始化Promise的時候Promise內(nèi)部的構(gòu)造器函數(shù)會立即執(zhí)行 因此 在這里會立即輸出一個3,所以這個3是第一個輸入的 new Promise(function (resolve) { console.log(3); }); // 然后輸入第二個輸出4 當(dāng)代碼執(zhí)行完畢后回去微任務(wù)隊列查找有沒有任務(wù),發(fā)現(xiàn)微任務(wù)隊列是空的,那么就去宏仁務(wù)隊列中查找,發(fā)現(xiàn)有一個我們剛剛放進(jìn)去的setTimeout回調(diào)函數(shù),那么就取出這個任務(wù)進(jìn)行執(zhí)行,所以緊接著輸出1 console.log(4);
看到上述的講解,大家是不是都明白了,是不是直呼簡單~
那我們接下來來看看node環(huán)境中的事件執(zhí)行環(huán)
NodeJs 事件環(huán)瀏覽器的 Event Loop 遵循的是 HTML5 標(biāo)準(zhǔn),而 NodeJs 的 Event Loop 遵循的是 libuv標(biāo)準(zhǔn),因此呢在事件的執(zhí)行中就會有一定的差異,大家都知道nodejs其實是js的一種runtime,也就是運行環(huán)境,那么在這種環(huán)境中nodejs的api大部分都是通過回調(diào)函數(shù),事件發(fā)布訂閱的方式來執(zhí)行的,那么在這樣的環(huán)境中我們代碼的執(zhí)行順序究竟是怎么樣的呢,也就是我們不同的回調(diào)函數(shù)究竟是怎么分類的然后是按照什么順序執(zhí)行的,其實就是由我們的libuv所決定的。
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
我們先來看下這六個任務(wù)是用來干什么的
timers: 這個階段執(zhí)行setTimeout()和setInterval()設(shè)定的回調(diào)。
pending callbacks: 上一輪循環(huán)中有少數(shù)的 I/O callback會被延遲到這一輪的這一階段執(zhí)行。
idle, prepare: 僅內(nèi)部使用。
poll: 執(zhí)行 I/O callback,在適當(dāng)?shù)臈l件下會阻塞在這個階段
check: 執(zhí)行setImmediate()設(shè)定的回調(diào)。
close callbacks: 執(zhí)行比如socket.on("close", ...)的回調(diào)。
我們再來看網(wǎng)上找到的一張nodejs執(zhí)行圖,我們能看到圖中有六個步驟 ,當(dāng)代碼執(zhí)行中如果我們遇到了這六個步驟中的回調(diào)函數(shù),就放入對應(yīng)的隊列中,然后當(dāng)我們同步人物執(zhí)行完畢的時候就會切換到下一個階段,也就是timer階段,然后timer階段執(zhí)行過程中會把這個階段的所有回調(diào)函數(shù)全部執(zhí)行了然后再進(jìn)入下一個階段,需要注意的是我們在每次階段發(fā)生切換的時候都會先執(zhí)行一次微任務(wù)隊列中的所有任務(wù),然后再進(jìn)入到下一個任務(wù)階段中去,所以我們就能總結(jié)出nodejs的事件環(huán)順序
同步代碼執(zhí)行,清空微任務(wù)隊列,執(zhí)行timer階段的回調(diào)函數(shù)(也就是setTimeout,setInterval)
全部執(zhí)行完畢,清空微任務(wù)隊列,執(zhí)行pending callbacks階段的回調(diào)函數(shù)
全部執(zhí)行完畢,清空微任務(wù)隊列,執(zhí)行idle, prepare階段的回調(diào)函數(shù)
全部執(zhí)行完畢,清空微任務(wù)隊列,執(zhí)行poll階段的回調(diào)函數(shù)
全部執(zhí)行完畢,清空微任務(wù)隊列,執(zhí)行check階段的回調(diào)函數(shù)(也就是setImmediate)
全部執(zhí)行完畢,清空微任務(wù)隊列,執(zhí)行close callbacks階段的回調(diào)函數(shù)
然后循環(huán)1-6階段
那我們來練練手~~~
// 我們來對著我們的執(zhí)行階段看看 let fs = require("fs"); // 遇到setTimeout 放入timer回調(diào)中 setTimeout(function(){ Promise.resolve().then(()=>{ console.log("then1"); }) },0); // 放入微任務(wù)隊列中 Promise.resolve().then(()=>{ console.log("then2"); }); // i/o操作 放入pending callbacks回調(diào)中 fs.readFile("./text.md",function(){ // 放入check階段 setImmediate(()=>{ console.log("setImmediate") }); // 放入微任務(wù)隊列中 process.nextTick(function(){ console.log("nextTick") }) });
首先同步代碼執(zhí)行完畢,我們先清空微任務(wù),此時輸出then2,然后切換到timer階段,執(zhí)行timer回調(diào),輸出then1,然后執(zhí)行i/o操作回調(diào),然后清空微任務(wù)隊列,輸出nextTick,接著進(jìn)入check階段,清空check階段回調(diào)輸出setImmediate
所有的規(guī)則看著都云里霧里,但是呢只要我們總結(jié)出來了規(guī)律,理解了他們的運行機(jī)制那么我們就掌握了這些規(guī)則,好咯,今天又學(xué)了這么多,不說了不說了,趕緊滾去寫業(yè)務(wù)代碼了.............
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/96724.html
摘要:概述本文主要介紹了我對的一些核心特性的理解,包括架構(gòu)特點機(jī)制核心模塊與簡單應(yīng)用。在此期間,主線程繼續(xù)執(zhí)行其他任務(wù)。延續(xù)了瀏覽器端單線程,只用一個主線程執(zhí)行,不斷循環(huán)遍歷事件隊列,執(zhí)行事件。 原文地址在我的博客,轉(zhuǎn)載請注明來源,謝謝! node是在前端領(lǐng)域經(jīng)常看到的詞。node對于前端的重要性已經(jīng)不言而喻,掌握node也是作為合格的前端工程師一項基本功了。知道node、知道后端的一些東西...
摘要:考慮了一段時間之后,終于鼓起勇氣找到老板離了職,去了一個北京的某培訓(xùn)機(jī)構(gòu),進(jìn)行了個月的加工,每天學(xué)習(xí)到凌晨點,新鮮出爐,滿懷信心的去面試。 12年高中畢業(yè)后,因高考失誤而停止學(xué)業(yè),轉(zhuǎn)戰(zhàn)維修行業(yè),在經(jīng)過3月的培訓(xùn)從小白成長維修大佬,在筆記本維修行業(yè)中摸爬滾打了,近3年也算是在行業(yè)中小有名氣,日子過得十分悠閑,每當(dāng)修好一片主板那種喜悅無法表達(dá)。 showImg(https://segmen...
摘要:我的個人博客地址資源地址非父子組件傳值,事件總線的使用方式我的博客地址如果您對我的博客內(nèi)容有疑惑或質(zhì)疑的地方,請在下方評論區(qū)留言,或郵件給我,共同學(xué)習(xí)進(jìn)步。 歡迎訪問我的個人博客:http://www.xiaolongwu.cn 前言 先說一下什么是事件總線,其實就是訂閱發(fā)布者模式; 比如有一個bus對象,這個對象上有兩個方法,一個是on(監(jiān)聽,也就是訂閱),一個是emit(觸發(fā),也就...
摘要:算法名稱描述優(yōu)點缺點標(biāo)記清除算法暫停除了線程以外的所有線程算法分為標(biāo)記和清除兩個階段首1 回顧我的時間線 在本文的開頭,先分享一下自己的春招經(jīng)歷吧: 各位掘友大家好,我是練習(xí)時長快一年的Android小蔡雞,喜歡看源碼,逛掘金,寫技術(shù)文章...... 好了好,不開玩笑了OWO,今年春招投了許多簡歷的,但是被撈的只有阿里,頭條和美團(tuán),一路下來挺不容易的. 個人認(rèn)為在春招中運氣>性格>三觀>技術(shù)...
閱讀 1689·2021-10-13 09:39
閱讀 3163·2021-10-12 10:11
閱讀 557·2021-09-28 09:36
閱讀 2641·2019-08-30 15:55
閱讀 1391·2019-08-30 13:04
閱讀 634·2019-08-29 17:08
閱讀 1913·2019-08-29 14:14
閱讀 3407·2019-08-28 18:23