目錄
堆疊和佇列
單線程和非同步
浏览器中的事件环
Node中的事件环
尾巴
首頁 web前端 js教程 Javascript事件環該如何理解? (圖文)

Javascript事件環該如何理解? (圖文)

Sep 08, 2018 pm 05:46 PM
javascript node.js

本篇文章帶給大家的內容是關於Javascript事件環該如何理解? (圖文),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

堆疊和佇列

在電腦記憶體中存取數據,基本的資料結構分為堆疊和佇列。

堆疊(Stack)是一種後進先出的資料結構,注意,有時候也管棧叫做“堆疊”,但是“堆”又是另一種複雜的資料結構,它和堆疊完全是兩碼事。棧的特徵是操作只在一端進行,一般來說,棧的操作只有兩種:進棧和出棧。第一個進棧的資料總是最後一個才出來。

Javascript事件環該如何理解? (圖文)

佇列(Queue)和堆疊類似,但它是先進先出的資料結構,插入資料的操作從佇列的一端進行,而刪除的操作在另一端。

Javascript事件環該如何理解? (圖文)

通俗的比喻堆疊就像是立好的桶,先放入堆疊的資料會放在桶底,出棧時會在桶口一一將資料取出,所以最先放入棧的資料總是最後一個才能取出。而隊列就像是一個水管,最先放入隊列的資料會第一個從隊列的另一端流出,這是它們最大的差異。

在javascript中,函數的執行就一個典型的入棧與出棧的過程:

function fun1() {
    function fun2() {
        function fun3() {
            console.log('do it');
        }
        fun3();
    }
    fun2();
}
fun1();
登入後複製

在程式執行時,首先將fun1,fun2,fun3依次入棧,而在呼叫函數時,是先將fun3呼叫(出棧),再是fun2和fun1,試想一下,如果fun1先出棧,那麼函數fun2和fun3必將遺失。

單線程和非同步

在javascript這門語言中程式是單線程的,只有一個主線程,這是為什麼?因為不難想像,最初javascript的設計是跑在瀏覽器中的腳本語言,如果設計成多線程,兩個線程同時修改DOM那以誰的為準呢?所以javascript為單線程,在一個線程中程式碼會一句一句往下走,直到程式跑完,若中間有較為費時的操作,那也只能等著。

單執行緒的設計使得語言的執行效率很差,為了利用多核心CPU的效能,javascript語言支援非同步程式碼,當有較為費時的操作時,可將任務寫為非同步執行,當一個當非同步任務還沒執行完時,主執行緒會將非同步任務掛起,繼續執行後面的同步程式碼,之後再回過頭來看,如果有非同步任務運行完了再執行它。

這種執行程式碼的方式其實很符合我們生活中的許多場景,例如小明同學下班回家了,他很渴,想燒水泡茶,如果是同步的執行方式那就是燒水,在水沒開時小明像個傻子似的等著,等水開了再泡茶;若是異步執行,小明先開始燒水,然後就去乾點別的事,比如看會電視、聽聽音樂,等水燒開了再去泡茶。明顯第二種非同步方式效率較高。

常見的非同步操作都有哪些?有很多,我們可以羅列幾個常見的:

  • Ajax

  • #DOM的事件操作

  • setTimeout

  • Promise的then方法

  • #Node的讀取檔

##我們先來看一段程式碼:

//示例1
console.log(1);
setTimeout(function () {
    console.log(2);
}, 1000);
console.log(3);
登入後複製
這段程式碼非常簡單,把它們放在瀏覽器中執行結果如下:

1
3
2
登入後複製
登入後複製
因為setTimeout函數延時了1000毫秒執行,因此先輸出1和3,而2是過了1000毫秒之後再輸出,這很合邏輯。

我們稍稍改動一下程式碼,將setTimeout的延時時間改為0:

//示例2
console.log(1);
setTimeout(function () {
    console.log(2);
}, 0); //0毫秒,不延时
console.log(3);
登入後複製
運行結果:

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);
登入後複製
運行結果:

1
4
3
2
登入後複製
以上三段程式碼,如果你能正確的寫出結果,並且能說明白為什麼這樣輸出,說明你對javascript的事件環理解的很清楚,如果講不出來,我們就一起聊聊這裡面發生了什麼,其實很有意思。

javascript是怎麼執行的?

一開始先簡單聊了一下基本的資料結構,它和我們現在說的事件環有什麼關係?當然有,首先要明確的一點是,

javascript程式碼的執行全都在堆疊裡,不論是同步程式碼還是非同步程式碼,這個一定要清楚。

而程式碼我們大體上分為了同步程式碼和非同步程式碼,其實非同步程式碼還可以再分為兩類:

巨集任務微任務

先別管什麼是宏任務和微任務,往往這種高大上的術語不利於我們理解,我們先這麼認為:宏,即是宏觀的、大的;微即微觀的、小的。

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

  1. 从上到下依次解释每一条js语句

  2. 若是同步任务,则压入一个栈(主线程);如果是异步任务,就放到一个任务队列里

  3. 开始执行栈里的同步任务,直到将栈里的所有任务都走完,此时栈清空了

  4. 回过头看异步队列里如果有异步任务完成了,就生成一个事件并注册回调,压入栈中

  5. 再返回第3步,直到异步队列都清空,程序运行结束

语言描述的费劲,不如看图:

Javascript事件環該如何理解? (圖文)

通过以上的步骤可以看到,不论是同步还是异步,只要是执行的时候都是要在栈里执行的,而一遍又一遍的回头检查异步队列,这种执行方式 就是所谓的“事件环”。

明白了javascript的执行原理,我们就不难理解之前的第二段代码,为什么setTimeout为0时会最后执行,因为setTimeout是异步代码,必须要等所有的同步代码都执行完,才会执行异步队列。即使setTimeout执行得再快,它也不可能在同步代码之前执行。

浏览器中的事件环

聊了这么多,我们好像还没有说宏任务和微任务的话题呢,上面说了,异步任务又分为微任务和宏任务,那它们又是一个怎样的执行机制呢?

注意!微任务和宏任务的执行方式在浏览器和Node中有差异,有差异!重要的事我们多说几遍,以下我们讨论的是在浏览器的环境里。

在浏览器的执行环境中,总是先执行小的、微任务,再执行大的、宏任务,回过头再看看第三段代码,为什么Promise的then方法在setTimeout之前执行?其根本原理就是因为Promise的then方法是一个微任务,而setTimeout是一个宏任务。

接下来我们借用阮一峰老师的一张图来说明:

Javascript事件環該如何理解? (圖文)

其实,以上这张图示我们可以再将它细化一点,这个图上的异步队列只画了一个,也就是说没有区分微任务队列和宏任务队列。我们可以脑补一下,在此图上多加一个微任务队列,当javascript执行时再多加一个判断,如果是微任务就加到微任务队列里,宏任务就加到宏任务队列里,在清空队列时,浏览器总会优先清空“微任务”。这样就把浏览器的事件环撤底说全了。

最后来一个大考,以下代码的运行结果是什么:

<script>
    setTimeout(function () {
        console.log(1);
        Promise.resolve().then(function () {
            console.log(2);
        });
    });
    setTimeout(function () {
        console.log(3);
    });
    Promise.resolve().then(function () {
        console.log(4);
    });
    console.log(5);
</script>
登入後複製

将此代码拷到chrome中跑一下,结果是:

5
4
1
2
3
登入後複製

不妨我们试着分析一下为什么是这个结果,首先输出5,因为console.log(5)是同步代码,这没什么可说的。

之后将前两个setTimeout和最后一个Promise放入异步队列,注意它们的区分,此时执行完了同步代码之后发现微任务和宏任务队列中都有代码,按浏览器的事件环机制,优先执行微任务,此时输出4。

然后执行宏任务队列里的第一个setTimeout,输出1。

此时,setTimeout中又有一个Promise,放入微任务队列。

再次清空微任务队列,输出2。

最后宏任务队列里还有最后一个setTimeout,输出3。

Node中的事件环

而Node中的事件环又和浏览器有些许的不同,在node.js的官方文档中有专门的描述,其中文档中有一张图,详细的说明了它的事件环机制,我们把它拿出来:

Javascript事件環該如何理解? (圖文)

可以看到,node.js中的事件环机制分为了6个阶段,其中最重要的3个阶段我在上面做了注明:

  • timer阶段,指的就是setTimeout等宏任务

  • poll轮询阶段,如读取文件等宏任务

  • check阶段,setImmediate宏任务

图中每一个阶段都代表了一个宏任务队列,在Node事件环中,微任务的运行时机是在每一个“宏任务队列”清空之后,在进入下一个宏任务队列之间执行。这是和浏览器的最大区别。

还是用代码说话吧,有一道经典的Node.js事件环面试题:

const fs = require('fs');

fs.readFile('./1.txt', (err, data) => {
    setTimeout(() => {
        console.log('timeout');
    });
    setImmediate(() => {
        console.log('immediate');
    });
    Promise.resolve().then(() => {
        console.log('Promise');
    });
});
登入後複製

运行结果:

Promise
immediate
timeout
登入後複製

代码并不复杂,首先使用fs模块读取了一个文件,在回调的内部有两个宏任务和一个微任务,微任务总是优于宏任务执行的,因此先输出Promise。

但是之后的区别为什么先输出immdiate?原因就在于fs读取文件的宏任务在上图中的第4个轮询阶段,当第4个阶段清空队列之后,就该进入第5个check阶段,也就是setImmediate这个宏任务所在的阶段,而不会跳回第1个阶段,因此先输出immedate。

尾巴

最后总结一下,分析完浏览器和Node的事件环发现它们并不简单,但只要记住了它们之间的区别就可以分析出结果。

浏览器事件环是运行完一个宏任务马上清空微任务队列
Node事件环是清空完一个阶段的宏任务队列之后再清空微任务队列

最后,总结一下常见的宏任务和微任务:

宏任务 微任务
setTimeout Promise的then方法
setInterval process.nextTick
setImmediate MutationObserver
MessageChannel

相关推荐:

细说JavaScript事件循环机制-第二讲

javascript实现线程以及事件循环详解

以上是Javascript事件環該如何理解? (圖文)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何使用WebSocket和JavaScript實現線上語音辨識系統 如何使用WebSocket和JavaScript實現線上語音辨識系統 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術 WebSocket與JavaScript:實現即時監控系統的關鍵技術 Dec 17, 2023 pm 05:30 PM

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統 如何利用JavaScript和WebSocket實現即時線上點餐系統 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統 如何使用WebSocket和JavaScript實現線上預約系統 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript與WebSocket:打造高效率的即時天氣預報系統 JavaScript與WebSocket:打造高效率的即時天氣預報系統 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

javascript如何使用insertBefore javascript如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用於在DOM樹中插入一個新的節點。這個方法需要兩個參數:要插入的新節點和參考節點(即新節點將要插入的位置的節點)。

JavaScript與WebSocket:打造高效率的即時影像處理系統 JavaScript與WebSocket:打造高效率的即時影像處理系統 Dec 17, 2023 am 08:41 AM

JavaScript是一種廣泛應用於Web開發的程式語言,而WebSocket則是一種用於即時通訊的網路協定。結合二者的強大功能,我們可以打造一個高效率的即時影像處理系統。本文將介紹如何利用JavaScript和WebSocket來實作這個系統,並提供具體的程式碼範例。首先,我們需要明確指出即時影像處理系統的需求和目標。假設我們有一個攝影機設備,可以擷取即時的影像數

See all articles