成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

泡杯茶,我們坐下聊聊Javascript的事件環(huán)

douzifly / 3410人閱讀

摘要:是怎么執(zhí)行的一開始先簡單聊了聊基本的數(shù)據(jù)結(jié)構(gòu),它和我們現(xiàn)在說的事件環(huán)有什么關(guān)系么當(dāng)然有,首先要明確的一點(diǎn)是,代碼的執(zhí)行全都在棧里,不論是同步代碼還是異步代碼,這個(gè)一定要清楚。

棧和隊(duì)列

在計(jì)算機(jī)內(nèi)存中存取數(shù)據(jù),基本的數(shù)據(jù)結(jié)構(gòu)分為棧和隊(duì)列。

棧(Stack)是一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),注意,有時(shí)候也管棧叫做“堆?!?,但是“堆”又是另一種復(fù)雜的數(shù)據(jù)結(jié)構(gòu),它和棧完全是兩碼事。棧的特點(diǎn)是操作只在一端進(jìn)行,一般來說,棧的操作只有兩種:進(jìn)棧和出棧。第一個(gè)進(jìn)棧的數(shù)據(jù)總是最后一個(gè)才出來。

隊(duì)列(Queue)和棧類似,但它是先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),插入數(shù)據(jù)的操作從隊(duì)列的一端進(jìn)行,而刪除的操作在另一端。

通俗的比喻棧就像是一個(gè)立好的桶,先放入棧的數(shù)據(jù)會(huì)放在桶底,出棧時(shí)會(huì)在桶口一一將數(shù)據(jù)取出,所以最先放入棧的數(shù)據(jù)總是最后一個(gè)才能取出。而隊(duì)列就像是一個(gè)水管,最先放入隊(duì)列的數(shù)據(jù)會(huì)第一個(gè)從隊(duì)列的另一端流出,這是它們最大的區(qū)別。

在javascript中,函數(shù)的執(zhí)行就一個(gè)典型的入棧與出棧的過程:

function fun1() {
    function fun2() {
        function fun3() {
            console.log("do it");
        }
        fun3();
    }
    fun2();
}
fun1();

在程序執(zhí)行時(shí),首先將fun1,fun2,fun3依次入棧,而在調(diào)用函數(shù)時(shí),是先將fun3調(diào)用(出棧),再是fun2和fun1,試想一下,如果fun1先出棧,那么函數(shù)fun2和fun3必將丟失。

單線程和異步

在javascript這門語言中程序是單線程的,只有一個(gè)主線程,這是為什么?因?yàn)椴浑y想像,最初javascript的設(shè)計(jì)是跑在瀏覽器中的腳本語言,如果設(shè)計(jì)成多線程,兩個(gè)線程同時(shí)修改DOM那以誰的為準(zhǔn)呢?所以javascript為單線程,在一個(gè)線程中代碼會(huì)一句一句向下走,直到程序跑完,若中間有較為費(fèi)時(shí)的操作,那也只能等著。

單線程的設(shè)計(jì)使得語言的執(zhí)行效率很差,為了利用多核心CPU的性能,javascript語言支持異步代碼,當(dāng)有較為費(fèi)時(shí)的操作時(shí),可將任務(wù)寫為異步執(zhí)行,當(dāng)一個(gè)異步任務(wù)還沒有執(zhí)行完時(shí),主線程會(huì)將異步任務(wù)掛起,繼續(xù)執(zhí)行后面的同步代碼,之后再回過頭來看,如果有異步任務(wù)運(yùn)行完了再執(zhí)行它。

這種執(zhí)行代碼的方式其實(shí)很符合我們生活中的很多場景,比如小明同學(xué)下班回家了,他很渴,想燒水泡茶,如果是同步的執(zhí)行方式那就是燒水,在水沒開時(shí)小明像個(gè)傻子似的等著,等水開了再泡茶;若是異步執(zhí)行,小明先開始燒水,然后就去干點(diǎn)別的事,比如看會(huì)電視、聽聽音樂,等水燒開了再去泡茶。明顯第二種異步方式效率更高。

常見的異步操作都有哪些?有很多,我們可以羅列幾個(gè)常見的:

Ajax

DOM的事件操作

setTimeout

Promise的then方法

Node的讀取文件

我們先來看一段代碼:

//示例1
console.log(1);
setTimeout(function () {
    console.log(2);
}, 1000);
console.log(3);

這段代碼非常簡單,把它們放在瀏覽器中執(zhí)行結(jié)果如下:

1
3
2

因?yàn)閟etTimeout函數(shù)延時(shí)了1000毫秒執(zhí)行,因此先輸出1和3,而2是過了1000毫秒之后再輸出,這很合邏輯。

我們稍稍改動(dòng)一下代碼,將setTimeout的延時(shí)時(shí)間改為0:

//示例2
console.log(1);
setTimeout(function () {
    console.log(2);
}, 0); //0毫秒,不延時(shí)
console.log(3);

運(yùn)行結(jié)果:

1
3
2

為什么延時(shí)了0毫秒還是最后輸出的2?先別急,我們再來看一段代碼:

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

運(yùn)行結(jié)果:

1
4
3
2

以上三段代碼,如果你能正確的寫出結(jié)果,并且能說明白為什么這樣輸出,說明你對javascript的事件環(huán)理解的很清楚,如果講不出來,我們就一起聊聊這里面發(fā)生了什么,其實(shí)很有意思。

javascript是怎么執(zhí)行的?

一開始先簡單聊了聊基本的數(shù)據(jù)結(jié)構(gòu),它和我們現(xiàn)在說的事件環(huán)有什么關(guān)系么?當(dāng)然有,首先要明確的一點(diǎn)是,javascript代碼的執(zhí)行全都在棧里,不論是同步代碼還是異步代碼,這個(gè)一定要清楚。

而代碼我們大體上分為了同步代碼和異步代碼,其實(shí)異步代碼還可以再分為兩類:宏任務(wù)微任務(wù)。

先別管什么是宏任務(wù)和微任務(wù),往往這種高大上的術(shù)語不利于我們理解,我們先這么認(rèn)為:宏,即是宏觀的、大的;微即微觀的、小的。

javascript是解釋型語言,它的執(zhí)行過程是這樣的:

從上到下依次解釋每一條js語句

若是同步任務(wù),則壓入一個(gè)棧(主線程);如果是異步任務(wù),就放到一個(gè)任務(wù)隊(duì)列里

開始執(zhí)行棧里的同步任務(wù),直到將棧里的所有任務(wù)都走完,此時(shí)棧清空了

回過頭看異步隊(duì)列里如果有異步任務(wù)完成了,就生成一個(gè)事件并注冊回調(diào),壓入棧中

再返回第3步,直到異步隊(duì)列都清空,程序運(yùn)行結(jié)束

語言描述的費(fèi)勁,不如看圖:

通過以上的步驟可以看到,不論是同步還是異步,只要是執(zhí)行的時(shí)候都是要在棧里執(zhí)行的,而一遍又一遍的回頭檢查異步隊(duì)列,這種執(zhí)行方式 就是所謂的“事件環(huán)”。

明白了javascript的執(zhí)行原理,我們就不難理解之前的第二段代碼,為什么setTimeout為0時(shí)會(huì)最后執(zhí)行,因?yàn)閟etTimeout是異步代碼,必須要等所有的同步代碼都執(zhí)行完,才會(huì)執(zhí)行異步隊(duì)列。即使setTimeout執(zhí)行得再快,它也不可能在同步代碼之前執(zhí)行。

瀏覽器中的事件環(huán)

聊了這么多,我們好像還沒有說宏任務(wù)和微任務(wù)的話題呢,上面說了,異步任務(wù)又分為微任務(wù)和宏任務(wù),那它們又是一個(gè)怎樣的執(zhí)行機(jī)制呢?

注意!微任務(wù)和宏任務(wù)的執(zhí)行方式在瀏覽器和Node中有差異,有差異!重要的事我們多說幾遍,以下我們討論的是在瀏覽器的環(huán)境里。

在瀏覽器的執(zhí)行環(huán)境中,總是先執(zhí)行小的、微任務(wù),再執(zhí)行大的、宏任務(wù),回過頭再看看第三段代碼,為什么Promise的then方法在setTimeout之前執(zhí)行?其根本原理就是因?yàn)镻romise的then方法是一個(gè)微任務(wù),而setTimeout是一個(gè)宏任務(wù)。

接下來我們借用阮一峰老師的一張圖來說明:

其實(shí),以上這張圖示我們可以再將它細(xì)化一點(diǎn),這個(gè)圖上的異步隊(duì)列只畫了一個(gè),也就是說沒有區(qū)分微任務(wù)隊(duì)列和宏任務(wù)隊(duì)列。我們可以腦補(bǔ)一下,在此圖上多加一個(gè)微任務(wù)隊(duì)列,當(dāng)javascript執(zhí)行時(shí)再多加一個(gè)判斷,如果是微任務(wù)就加到微任務(wù)隊(duì)列里,宏任務(wù)就加到宏任務(wù)隊(duì)列里,在清空隊(duì)列時(shí),瀏覽器總會(huì)優(yōu)先清空“微任務(wù)”。這樣就把瀏覽器的事件環(huán)撤底說全了。

最后來一個(gè)大考,以下代碼的運(yùn)行結(jié)果是什么:

將此代碼拷到chrome中跑一下,結(jié)果是:

5
4
1
2
3

不妨我們試著分析一下為什么是這個(gè)結(jié)果,首先輸出5,因?yàn)?b>console.log(5)是同步代碼,這沒什么可說的。

之后將前兩個(gè)setTimeout和最后一個(gè)Promise放入異步隊(duì)列,注意它們的區(qū)分,此時(shí)執(zhí)行完了同步代碼之后發(fā)現(xiàn)微任務(wù)和宏任務(wù)隊(duì)列中都有代碼,按瀏覽器的事件環(huán)機(jī)制,優(yōu)先執(zhí)行微任務(wù),此時(shí)輸出4。

然后執(zhí)行宏任務(wù)隊(duì)列里的第一個(gè)setTimeout,輸出1。

此時(shí),setTimeout中又有一個(gè)Promise,放入微任務(wù)隊(duì)列。

再次清空微任務(wù)隊(duì)列,輸出2。

最后宏任務(wù)隊(duì)列里還有最后一個(gè)setTimeout,輸出3。

Node中的事件環(huán)

而Node中的事件環(huán)又和瀏覽器有些許的不同,在node.js的官方文檔中有專門的描述,其中文檔中有一張圖,詳細(xì)的說明了它的事件環(huán)機(jī)制,我們把它拿出來:

可以看到,node.js中的事件環(huán)機(jī)制分為了6個(gè)階段,其中最重要的3個(gè)階段我在上面做了注明:

timer階段,指的就是setTimeout等宏任務(wù)

poll輪詢階段,如讀取文件等宏任務(wù)

check階段,setImmediate宏任務(wù)

圖中每一個(gè)階段都代表了一個(gè)宏任務(wù)隊(duì)列,在Node事件環(huán)中,微任務(wù)的運(yùn)行時(shí)機(jī)是在每一個(gè)“宏任務(wù)隊(duì)列”清空之后,在進(jìn)入下一個(gè)宏任務(wù)隊(duì)列之間執(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");
    });
});

運(yùn)行結(jié)果:

Promise
immediate
timeout

代碼并不復(fù)雜,首先使用fs模塊讀取了一個(gè)文件,在回調(diào)的內(nèi)部有兩個(gè)宏任務(wù)和一個(gè)微任務(wù),微任務(wù)總是優(yōu)于宏任務(wù)執(zhí)行的,因此先輸出Promise。

但是之后的區(qū)別為什么先輸出immdiate?原因就在于fs讀取文件的宏任務(wù)在上圖中的第4個(gè)輪詢階段,當(dāng)?shù)?個(gè)階段清空隊(duì)列之后,就該進(jìn)入第5個(gè)check階段,也就是setImmediate這個(gè)宏任務(wù)所在的階段,而不會(huì)跳回第1個(gè)階段,因此先輸出immedate。

尾巴

最后總結(jié)一下,分析完瀏覽器和Node的事件環(huán)發(fā)現(xiàn)它們并不簡單,但只要記住了它們之間的區(qū)別就可以分析出結(jié)果。

瀏覽器事件環(huán)是運(yùn)行完一個(gè)宏任務(wù)馬上清空微任務(wù)隊(duì)列。
Node事件環(huán)是清空完一個(gè)階段的宏任務(wù)隊(duì)列之后再清空微任務(wù)隊(duì)列。

最后,總結(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://systransis.cn/yun/97544.html

相關(guān)文章

  • 設(shè)計(jì)模式系列·Facade模式之MVC煩惱

    摘要:沒有任何意外,王小二的公司用來開發(fā)公司的主打產(chǎn)品。臃腫的著手開干吧小二打開熟悉的,找到提交訂單模塊的。要不再去請教下哥的煩惱小二找到哥,詳細(xì)的描述了他的問題。 流行的MVC架構(gòu)模式 如今的Web開發(fā),各種框架風(fēng)起云涌,勢如破竹。 從國民第一的ThinkPhp到稱霸全球的Laravel,這些框架有一個(gè)共同特征,都采用了MVC的架構(gòu)模式。 showImg(https://segmentfa...

    zhichangterry 評論0 收藏0
  • 編程范式 —— 函數(shù)式編程入門

    摘要:在函數(shù)式編程中數(shù)據(jù)在由純函數(shù)組成的管道中傳遞。函數(shù)式編程中函子是實(shí)現(xiàn)了函數(shù)的容器下文中將函子視為范疇,模型可表示如下但是在函數(shù)式編程中要避免使用這種面向?qū)ο蟮木幊谭绞饺《畬ν獗┞读艘粋€(gè)的接口也稱為。 showImg(https://segmentfault.com/img/remote/1460000018101204); 該系列會(huì)有 3 篇文章,分別介紹什么是函數(shù)式編程、剖析函數(shù)...

    flyer_dev 評論0 收藏0
  • 使用fjpublish發(fā)布前端項(xiàng)目(基礎(chǔ)篇)

    摘要:任務(wù)描述使用的來發(fā)布多個(gè)目錄使用的忽略所有結(jié)尾的文件。任務(wù)描述使用的配置項(xiàng)在項(xiàng)目發(fā)布后重啟進(jìn)程忽略當(dāng)次構(gòu)建過程并提交一次使用的配置項(xiàng)設(shè)置每個(gè)遠(yuǎn)程命令超時(shí)時(shí)間為秒。下一期地址使用發(fā)布前端項(xiàng)目安全篇官方交流群 本系列文章共分為基礎(chǔ)篇,安全篇,拓展篇。 前言 曾幾何時(shí),我相信部分Web Developer(包括我)使用的項(xiàng)目發(fā)布方式比較傳統(tǒng)(使用xftp或者sublime text的插件sf...

    anonymoussf 評論0 收藏0
  • 聊聊幽靈Class

    摘要:有鎖的地方就會(huì)有鎖競爭,并且也是一個(gè)耗時(shí)的過程,所以同一個(gè)如果并發(fā)出現(xiàn)在日志堆棧中勢必會(huì)導(dǎo)致一部分線程會(huì),這對于線上系統(tǒng)中簡直就是災(zāi)難改進(jìn)在最近的版本中并沒有發(fā)現(xiàn)對的類做特殊處理,并且正如官方說的所以只要的版本大于,這個(gè)配置默認(rèn)都是關(guān)閉的 起因 偶然一次路過同事電腦,看著黑底藍(lán)色滿屏的堆棧信息,過去笑著拍了拍他的肩膀說道「小哥,又在寫B(tài)UG呢」湊過去仔細(xì)看了一眼異常堆棧詳情,「虎軀一震...

    Ilikewhite 評論0 收藏0
  • 牛仔很忙之大話HTTPS

    摘要:移動(dòng)組的壕星在灌水群里發(fā)了條消息,這家伙又準(zhǔn)備顯擺他的腎了。什么是小夏弱弱的問。配備身份證書,防止身份被冒充。那這么說,豈不是將來所有網(wǎng)站都會(huì)用小夏接著道,又加了個(gè)羞澀的表情。 中秋前,本牛仔正在苦思新任務(wù)的對策,突然桌面右下角的小企鵝一陣跳動(dòng):剛收到消息, 第三方下載的XCode被植入后門,N多app信息疑似泄密。還是你們用安卓的好啊,唉。 移動(dòng)組的壕星在灌水群里發(fā)了條消息,這家伙又...

    MonoLog 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<