摘要:是怎么執(zhí)行的一開始先簡單聊了聊基本的數(shù)據(jù)結(jié)構(gòu),它和我們現(xiàn)在說的事件環(huán)有什么關(guān)系么當(dāng)然有,首先要明確的一點是,代碼的執(zhí)行全都在棧里,不論是同步代碼還是異步代碼,這個一定要清楚。
棧和隊列
在計算機內(nèi)存中存取數(shù)據(jù),基本的數(shù)據(jù)結(jié)構(gòu)分為棧和隊列。
棧(Stack)是一種后進先出的數(shù)據(jù)結(jié)構(gòu),注意,有時候也管棧叫做“堆棧”,但是“堆”又是另一種復(fù)雜的數(shù)據(jù)結(jié)構(gòu),它和棧完全是兩碼事。棧的特點是操作只在一端進行,一般來說,棧的操作只有兩種:進棧和出棧。第一個進棧的數(shù)據(jù)總是最后一個才出來。
隊列(Queue)和棧類似,但它是先進先出的數(shù)據(jù)結(jié)構(gòu),插入數(shù)據(jù)的操作從隊列的一端進行,而刪除的操作在另一端。
通俗的比喻棧就像是一個立好的桶,先放入棧的數(shù)據(jù)會放在桶底,出棧時會在桶口一一將數(shù)據(jù)取出,所以最先放入棧的數(shù)據(jù)總是最后一個才能取出。而隊列就像是一個水管,最先放入隊列的數(shù)據(jù)會第一個從隊列的另一端流出,這是它們最大的區(qū)別。
在javascript中,函數(shù)的執(zhí)行就一個典型的入棧與出棧的過程:
function fun1() { function fun2() { function fun3() { console.log("do it"); } fun3(); } fun2(); } fun1();
在程序執(zhí)行時,首先將fun1,fun2,fun3依次入棧,而在調(diào)用函數(shù)時,是先將fun3調(diào)用(出棧),再是fun2和fun1,試想一下,如果fun1先出棧,那么函數(shù)fun2和fun3必將丟失。
單線程和異步在javascript這門語言中程序是單線程的,只有一個主線程,這是為什么?因為不難想像,最初javascript的設(shè)計是跑在瀏覽器中的腳本語言,如果設(shè)計成多線程,兩個線程同時修改DOM那以誰的為準(zhǔn)呢?所以javascript為單線程,在一個線程中代碼會一句一句向下走,直到程序跑完,若中間有較為費時的操作,那也只能等著。
單線程的設(shè)計使得語言的執(zhí)行效率很差,為了利用多核心CPU的性能,javascript語言支持異步代碼,當(dāng)有較為費時的操作時,可將任務(wù)寫為異步執(zhí)行,當(dāng)一個異步任務(wù)還沒有執(zhí)行完時,主線程會將異步任務(wù)掛起,繼續(xù)執(zhí)行后面的同步代碼,之后再回過頭來看,如果有異步任務(wù)運行完了再執(zhí)行它。
這種執(zhí)行代碼的方式其實很符合我們生活中的很多場景,比如小明同學(xué)下班回家了,他很渴,想燒水泡茶,如果是同步的執(zhí)行方式那就是燒水,在水沒開時小明像個傻子似的等著,等水開了再泡茶;若是異步執(zhí)行,小明先開始燒水,然后就去干點別的事,比如看會電視、聽聽音樂,等水燒開了再去泡茶。明顯第二種異步方式效率更高。
常見的異步操作都有哪些?有很多,我們可以羅列幾個常見的:
Ajax
DOM的事件操作
setTimeout
Promise的then方法
Node的讀取文件
我們先來看一段代碼:
//示例1 console.log(1); setTimeout(function () { console.log(2); }, 1000); console.log(3);
這段代碼非常簡單,把它們放在瀏覽器中執(zhí)行結(jié)果如下:
1 3 2
因為setTimeout函數(shù)延時了1000毫秒執(zhí)行,因此先輸出1和3,而2是過了1000毫秒之后再輸出,這很合邏輯。
我們稍稍改動一下代碼,將setTimeout的延時時間改為0:
//示例2 console.log(1); setTimeout(function () { console.log(2); }, 0); //0毫秒,不延時 console.log(3);
運行結(jié)果:
1 3 2
為什么延時了0毫秒還是最后輸出的2?先別急,我們再來看一段代碼:
//示例3 console.log(1); setTimeout(function () { console.log(2); }, 0); Promise.resolve().then(function(){ console.log(3); }); console.log(4);
運行結(jié)果:
1 4 3 2
以上三段代碼,如果你能正確的寫出結(jié)果,并且能說明白為什么這樣輸出,說明你對javascript的事件環(huán)理解的很清楚,如果講不出來,我們就一起聊聊這里面發(fā)生了什么,其實很有意思。
javascript是怎么執(zhí)行的?一開始先簡單聊了聊基本的數(shù)據(jù)結(jié)構(gòu),它和我們現(xiàn)在說的事件環(huán)有什么關(guān)系么?當(dāng)然有,首先要明確的一點是,javascript代碼的執(zhí)行全都在棧里,不論是同步代碼還是異步代碼,這個一定要清楚。
而代碼我們大體上分為了同步代碼和異步代碼,其實異步代碼還可以再分為兩類:宏任務(wù)和微任務(wù)。
先別管什么是宏任務(wù)和微任務(wù),往往這種高大上的術(shù)語不利于我們理解,我們先這么認(rèn)為:宏,即是宏觀的、大的;微即微觀的、小的。
javascript是解釋型語言,它的執(zhí)行過程是這樣的:
從上到下依次解釋每一條js語句
若是同步任務(wù),則壓入一個棧(主線程);如果是異步任務(wù),就放到一個任務(wù)隊列里
開始執(zhí)行棧里的同步任務(wù),直到將棧里的所有任務(wù)都走完,此時棧清空了
回過頭看異步隊列里如果有異步任務(wù)完成了,就生成一個事件并注冊回調(diào),壓入棧中
再返回第3步,直到異步隊列都清空,程序運行結(jié)束
語言描述的費勁,不如看圖:
通過以上的步驟可以看到,不論是同步還是異步,只要是執(zhí)行的時候都是要在棧里執(zhí)行的,而一遍又一遍的回頭檢查異步隊列,這種執(zhí)行方式 就是所謂的“事件環(huán)”。
明白了javascript的執(zhí)行原理,我們就不難理解之前的第二段代碼,為什么setTimeout為0時會最后執(zhí)行,因為setTimeout是異步代碼,必須要等所有的同步代碼都執(zhí)行完,才會執(zhí)行異步隊列。即使setTimeout執(zhí)行得再快,它也不可能在同步代碼之前執(zhí)行。
瀏覽器中的事件環(huán)聊了這么多,我們好像還沒有說宏任務(wù)和微任務(wù)的話題呢,上面說了,異步任務(wù)又分為微任務(wù)和宏任務(wù),那它們又是一個怎樣的執(zhí)行機制呢?
注意!微任務(wù)和宏任務(wù)的執(zhí)行方式在瀏覽器和Node中有差異,有差異!重要的事我們多說幾遍,以下我們討論的是在瀏覽器的環(huán)境里。
在瀏覽器的執(zhí)行環(huán)境中,總是先執(zhí)行小的、微任務(wù),再執(zhí)行大的、宏任務(wù),回過頭再看看第三段代碼,為什么Promise的then方法在setTimeout之前執(zhí)行?其根本原理就是因為Promise的then方法是一個微任務(wù),而setTimeout是一個宏任務(wù)。
接下來我們借用阮一峰老師的一張圖來說明:
其實,以上這張圖示我們可以再將它細化一點,這個圖上的異步隊列只畫了一個,也就是說沒有區(qū)分微任務(wù)隊列和宏任務(wù)隊列。我們可以腦補一下,在此圖上多加一個微任務(wù)隊列,當(dāng)javascript執(zhí)行時再多加一個判斷,如果是微任務(wù)就加到微任務(wù)隊列里,宏任務(wù)就加到宏任務(wù)隊列里,在清空隊列時,瀏覽器總會優(yōu)先清空“微任務(wù)”。這樣就把瀏覽器的事件環(huán)撤底說全了。
最后來一個大考,以下代碼的運行結(jié)果是什么:
將此代碼拷到chrome中跑一下,結(jié)果是:
5 4 1 2 3
不妨我們試著分析一下為什么是這個結(jié)果,首先輸出5,因為console.log(5)是同步代碼,這沒什么可說的。
之后將前兩個setTimeout和最后一個Promise放入異步隊列,注意它們的區(qū)分,此時執(zhí)行完了同步代碼之后發(fā)現(xiàn)微任務(wù)和宏任務(wù)隊列中都有代碼,按瀏覽器的事件環(huán)機制,優(yōu)先執(zhí)行微任務(wù),此時輸出4。
然后執(zhí)行宏任務(wù)隊列里的第一個setTimeout,輸出1。
此時,setTimeout中又有一個Promise,放入微任務(wù)隊列。
再次清空微任務(wù)隊列,輸出2。
最后宏任務(wù)隊列里還有最后一個setTimeout,輸出3。
Node中的事件環(huán)而Node中的事件環(huán)又和瀏覽器有些許的不同,在node.js的官方文檔中有專門的描述,其中文檔中有一張圖,詳細的說明了它的事件環(huán)機制,我們把它拿出來:
可以看到,node.js中的事件環(huán)機制分為了6個階段,其中最重要的3個階段我在上面做了注明:
timer階段,指的就是setTimeout等宏任務(wù)
poll輪詢階段,如讀取文件等宏任務(wù)
check階段,setImmediate宏任務(wù)
圖中每一個階段都代表了一個宏任務(wù)隊列,在Node事件環(huán)中,微任務(wù)的運行時機是在每一個“宏任務(wù)隊列”清空之后,在進入下一個宏任務(wù)隊列之間執(zhí)行。這是和瀏覽器的最大區(qū)別。
還是用代碼說話吧,有一道經(jīng)典的Node.js事件環(huán)面試題:
const fs = require("fs"); fs.readFile("./1.txt", (err, data) => { setTimeout(() => { console.log("timeout"); }); setImmediate(() => { console.log("immediate"); }); Promise.resolve().then(() => { console.log("Promise"); }); });
運行結(jié)果:
Promise immediate timeout
代碼并不復(fù)雜,首先使用fs模塊讀取了一個文件,在回調(diào)的內(nèi)部有兩個宏任務(wù)和一個微任務(wù),微任務(wù)總是優(yōu)于宏任務(wù)執(zhí)行的,因此先輸出Promise。
但是之后的區(qū)別為什么先輸出immdiate?原因就在于fs讀取文件的宏任務(wù)在上圖中的第4個輪詢階段,當(dāng)?shù)?個階段清空隊列之后,就該進入第5個check階段,也就是setImmediate這個宏任務(wù)所在的階段,而不會跳回第1個階段,因此先輸出immedate。
尾巴最后總結(jié)一下,分析完瀏覽器和Node的事件環(huán)發(fā)現(xiàn)它們并不簡單,但只要記住了它們之間的區(qū)別就可以分析出結(jié)果。
瀏覽器事件環(huán)是運行完一個宏任務(wù)馬上清空微任務(wù)隊列。
Node事件環(huán)是清空完一個階段的宏任務(wù)隊列之后再清空微任務(wù)隊列。
最后,總結(jié)一下常見的宏任務(wù)和微任務(wù):
宏任務(wù) | 微任務(wù) |
---|---|
setTimeout | Promise的then方法 |
setInterval | process.nextTick |
setImmediate | MutationObserver |
MessageChannel |
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/97544.html
摘要:沒有任何意外,王小二的公司用來開發(fā)公司的主打產(chǎn)品。臃腫的著手開干吧小二打開熟悉的,找到提交訂單模塊的。要不再去請教下哥的煩惱小二找到哥,詳細的描述了他的問題。 流行的MVC架構(gòu)模式 如今的Web開發(fā),各種框架風(fēng)起云涌,勢如破竹。 從國民第一的ThinkPhp到稱霸全球的Laravel,這些框架有一個共同特征,都采用了MVC的架構(gòu)模式。 showImg(https://segmentfa...
摘要:在函數(shù)式編程中數(shù)據(jù)在由純函數(shù)組成的管道中傳遞。函數(shù)式編程中函子是實現(xiàn)了函數(shù)的容器下文中將函子視為范疇,模型可表示如下但是在函數(shù)式編程中要避免使用這種面向?qū)ο蟮木幊谭绞饺《畬ν獗┞读艘粋€的接口也稱為。 showImg(https://segmentfault.com/img/remote/1460000018101204); 該系列會有 3 篇文章,分別介紹什么是函數(shù)式編程、剖析函數(shù)...
摘要:任務(wù)描述使用的來發(fā)布多個目錄使用的忽略所有結(jié)尾的文件。任務(wù)描述使用的配置項在項目發(fā)布后重啟進程忽略當(dāng)次構(gòu)建過程并提交一次使用的配置項設(shè)置每個遠程命令超時時間為秒。下一期地址使用發(fā)布前端項目安全篇官方交流群 本系列文章共分為基礎(chǔ)篇,安全篇,拓展篇。 前言 曾幾何時,我相信部分Web Developer(包括我)使用的項目發(fā)布方式比較傳統(tǒng)(使用xftp或者sublime text的插件sf...
摘要:有鎖的地方就會有鎖競爭,并且也是一個耗時的過程,所以同一個如果并發(fā)出現(xiàn)在日志堆棧中勢必會導(dǎo)致一部分線程會,這對于線上系統(tǒng)中簡直就是災(zāi)難改進在最近的版本中并沒有發(fā)現(xiàn)對的類做特殊處理,并且正如官方說的所以只要的版本大于,這個配置默認(rèn)都是關(guān)閉的 起因 偶然一次路過同事電腦,看著黑底藍色滿屏的堆棧信息,過去笑著拍了拍他的肩膀說道「小哥,又在寫B(tài)UG呢」湊過去仔細看了一眼異常堆棧詳情,「虎軀一震...
摘要:移動組的壕星在灌水群里發(fā)了條消息,這家伙又準(zhǔn)備顯擺他的腎了。什么是小夏弱弱的問。配備身份證書,防止身份被冒充。那這么說,豈不是將來所有網(wǎng)站都會用小夏接著道,又加了個羞澀的表情。 中秋前,本牛仔正在苦思新任務(wù)的對策,突然桌面右下角的小企鵝一陣跳動:剛收到消息, 第三方下載的XCode被植入后門,N多app信息疑似泄密。還是你們用安卓的好啊,唉。 移動組的壕星在灌水群里發(fā)了條消息,這家伙又...
閱讀 1588·2021-09-23 11:21
閱讀 2352·2021-09-07 10:13
閱讀 840·2021-09-02 10:19
閱讀 1134·2019-08-30 15:44
閱讀 1728·2019-08-30 13:18
閱讀 1918·2019-08-30 11:15
閱讀 1111·2019-08-29 17:17
閱讀 2021·2019-08-29 15:31