用一道大廠面試題帶你搞懂事件迴圈機制

来源:https://www.cnblogs.com/Vay123/archive/2019/12/27/12108188.html
-Advertisement-
Play Games

本文涵蓋 面試題的引入 對事件迴圈面試題執行順序的一些疑問 通過面試題對微任務、事件迴圈、定時器等對深入理解 結論總結 面試題 面試題如下,大家可以先試著寫一下輸出結果,然後再看我下麵的詳細講解,看看會不會有什麼出入,如果把整個順序弄清楚 Node.js 的執行順序應該就沒問題了。 async fu ...


本文涵蓋

  • 面試題的引入
  • 對事件迴圈面試題執行順序的一些疑問
  • 通過面試題對微任務、事件迴圈、定時器等對深入理解
  • 結論總結

面試題

面試題如下,大家可以先試著寫一下輸出結果,然後再看我下麵的詳細講解,看看會不會有什麼出入,如果把整個順序弄清楚 Node.js 的執行順序應該就沒問題了。

async function async1(){    console.log('async1 start')    await async2()    console.log('async1 end')  }async function async2(){    console.log('async2')}console.log('script start')setTimeout(function(){    console.log('setTimeout0') },0)  setTimeout(function(){    console.log('setTimeout3') },3)  setImmediate(() => console.log('setImmediate'));process.nextTick(() => console.log('nextTick'));async1();new Promise(function(resolve){    console.log('promise1')    resolve();    console.log('promise2')}).then(function(){    console.log('promise3')})console.log('script end')

面試題正確的輸出結果

script startasync1 startasync2promise1promise2script endnextTickasync1 endpromise3setTimeout0setImmediatesetTimeout3

提出問題

在理解node.js的非同步的時候有一些不懂的地方,使用node.js的開發者一定都知道它是單線程的,非同步不阻塞且高併發的一門語言,但是node.js在實現非同步的時候,兩個非同步任務開啟了,是就是誰快就誰先完成這麼簡單,還是說非同步任務最後也會有一個先後執行順序?對於一個單線程的的非同步語言它是怎麼實現高併發的呢?

好接下來我們就帶著這兩個問題來真正的理解node.js中的非同步(微任務與事件迴圈)。

Node 的非同步語法比瀏覽器更複雜,因為它可以跟內核對話,不得不搞了一個專門的庫 libuv 做這件事。這個庫負責各種回調函數的執行時間,非同步任務最後基於事件迴圈機制還是要回到主線程,一個個排隊執行。

我自己是一名從事了多年開發的web前端老程式員,目前辭職在做自己的web前端私人定製課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習乾貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關註我並添加我的web前端交流裙:600610151,即可免費獲取。

詳細講解

1.本輪迴圈與次輪迴圈

非同步任務可以分成兩種。

  1. 追加在本輪迴圈的非同步任務
  2. 追加在次輪迴圈的非同步任務

所謂”迴圈”,指的是事件迴圈(event loop)。這是 JavaScript 引擎處理非同步任務的方式,後文會詳細解釋。這裡只要理解,本輪迴圈一定早於次輪迴圈執行即可。

Node 規定,process.nextTick和Promise的回調函數,追加在本輪迴圈,即同步任務一旦執行完成,就開始執行它們。而setTimeout、setInterval、setImmediate的回調函數,追加在次輪迴圈。

2.process.nextTick()

1)process.nextTick不要因為有next就被好多小伙伴當作次輪迴圈。

2)Node 執行完所有同步任務,接下來就會執行 process.nextTick 的任務隊列。

3)開發過程中如果想讓非同步任務儘可能快地執行,可以使用 process.nextTick 來完成。

3.微任務(microtack)

根據語言規格,Promise 對象的回調函數,會進入非同步任務裡面的”微任務”(microtask)隊列。

微任務隊列追加在 process.nextTick 隊列的後面,也屬於本輪迴圈。

根據語言規格,Promise 對象的回調函數,會進入非同步任務裡面的”微任務”(microtask)隊列。

微任務隊列追加在process.nextTick隊列的後面,也屬於本輪迴圈。所以,下麵的代碼總是先輸出3,再輸出4。

process.nextTick(() => console.log(3));Promise.resolve().then(() => console.log(4));

// 輸出結果3,4

process.nextTick(() => console.log(1));Promise.resolve().then(() => console.log(2));process.nextTick(() => console.log(3));Promise.resolve().then(() => console.log(4));

// 輸出結果 1,3,2,4

註意,只有前一個隊列全部清空以後,才會執行下一個隊列。兩個隊列的概念 nextTickQueue 和微隊列 microTaskQueue,也就是說開啟非同步任務也分為幾種,像 Promise 對象這種,開啟之後直接進入微隊列中,微隊列內的就是那個任務快就那個先執行完,但是針對於隊列與隊列之間不同的任務,還是會有先後順序,這個先後順序是由隊列決定的。

4.事件迴圈的階段(idle, prepare忽略了這個階段)

事件迴圈最階段最詳細的講解(官網:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout)

  1. timers階段次階段包括setTimeout()和setInterval()
  2. IO callbacks大部分的回調事件,普通的caollback
  3. poll階段網路連接,數據獲取,讀取文件等操作
  4. check階段setImmediate()在這裡調用回調
  5. close階段 一些關閉回調,例如socket.on('close', ...)
  • 事件迴圈註意點

1)Node 開始執行腳本時,會先進行事件迴圈的初始化,但是這時事件迴圈還沒有開始,會先 完成下麵的事情。

同步任務 發出非同步請求 規劃定時器生效的時間 執行process.nextTick()等等

最後,上面這些事情都幹完了,事件迴圈就正式開始了。

2)事件迴圈同樣運行在單線程環境下,高併發也是依靠事件迴圈,每產生一個事件,就會加入到該階段對應的隊列中,此時事件迴圈將該隊列中的事件取出,準備執行之後的 Callback。

3)假設事件迴圈現在進入了某個階段,即使這期間有其他隊列中的事件就緒,也會先將當前隊列的全部回調方法執行完畢後,再進入到下一個階段。

5.事件迴圈中的setTimeOut與setImmediate

由於 setTimeout 在 timers 階段執行,而 setImmediate 在 check 階段執行。所以,setTimeout 會早於 setImmediate 完成。

setTimeout(() => console.log(1));setImmediate(() => console.log(2));

上面代碼應該先輸出1,再輸出2,但是實際執行的時候,結果卻是不確定,有時還會先輸出2,再輸出1。

這是因為 setTimeout 的第二個參數預設為0。但是實際上,Node 做不到0毫秒,最少也需要1毫秒,根據官方文檔,第二個參數的取值範圍在1毫秒到2147483647毫秒之間。也就是說,setTimeout(f, 0)等同於setTimeout(f, 1)。

實際執行的時候,進入事件迴圈以後,有可能到了1毫秒,也可能還沒到1毫秒,取決於系統當時的狀況。如果沒到1毫秒,那麼 timers 階段就會跳過,進入 check 階段,先執行 setImmediate 的回調函數。

但是,下麵的代碼一定是先輸出2,再輸出1。

const fs = require('fs');fs.readFile('test.js', () => { setTimeout(() => console.log(1)); setImmediate(() => console.log(2));});

上面代碼會先進入 I/O callbacks 階段,然後是 check 階段,最後才是 timers 階段。因此,setImmediate才會早於setTimeout執行。

6.同步任務中async以及promise的一些誤解

  • 問題1:

在面試題中,在同步任務的過程中,不知道大家有沒有疑問,為什麼不是執行完async2輸出後執行async1 end輸出,而是接著執行 promise1?

引用書中一句話:“ async 函數返回一個 Promise 對象,當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的非同步操作完成,再接著執行函數體內後面的語句。”

簡單的說,先去執行後面的同步任務代碼,執行完成後,也就是表達式中的 Promise 解析完成後繼續執行 async 函數並返回解決結果。(其實還是本輪迴圈promise的問題,最後的resolve屬於非同步,位於本輪迴圈的末尾。)

  • 問題2:

console.log('promise2')為什麼也是在resolve之前執行?

解答:註:此內容來源與阮一峰老師的ES6書籍,調用resolve或者reject並不會終結promise的參數函數的執行。因為立即resolved的Promise是本輪迴圈的末尾執行,同時總是晚於本輪迴圈的同步任務。正規的寫法調用resolve或者reject以後,Promise的使命就完成了,後繼操作應該放在then方法後面。所以最好在它的前面加上return語句,這樣就不會出現意外

new Promise((resolve,reject) => {    return resolve(1);    //後面的語句不會執行    console.log(2);}
  • 問題3:

promise3和script end的執行順序是否有疑問?

解答:因為立即resolved的Promise是本輪迴圈的末尾執行,同時總是晚於本輪迴圈的同步任務。Promise 是一個立即執行函數,但是他的成功(或失敗:reject)的回調函數 resolve 卻是一個非同步執行的回調。當執行到 resolve() 時,這個任務會被放入到回調隊列中,等待調用棧有空閑時事件迴圈再來取走它。本輪迴圈中最後執行的。

整體結論

用一道大廠面試題帶你搞懂事件迴圈機制

 

順序的整體總結就是: 同步任務-> 本輪迴圈->次輪迴圈


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • APK二次打包的危害 APK二次打包是Android應用安全風險中的一部分, 一般是通過反編譯工具嚮應用中插入廣告代碼與相關配置,再在第三方應用市場、論壇發佈。打包黨對移動App帶來的危害有以下幾種: 1. 插入自己廣告或者刪除原來廣告; 2. 惡意代碼, 惡意扣費、木馬等; 3. 修改原來支付邏輯 ...
  • 這是一篇文字超多的博客,哈哈哈,廢話自行過濾··· 遇到問題 在開發中我們常會在ListView , RecycleView 列表中添加EditText輸入框,或者checkbox覆選框。 覆選框應該是用的比較多的,輸入框淘寶採用的是彈出框的方式,可能在一些特定的情況下,我們希望能夠直接在列表中輸入 ...
  • Janus說明 Android APP僅使用V1簽名,可能存在Janus漏洞(CVE 2017 13156),Janus漏洞(CVE 2017 13156)允許攻擊者在不改變原簽名的情況下任意修改APP中的代碼邏輯。 影響範圍:Android系統5.1.1 8.0 檢測方式 方式1 使用GetApk ...
  • MAC停靠欄 ~~~javascript ~~~ ...
  • 實例對象使用屬性和方法層層的搜索: 實例對象使用的屬性或者方法, 先在實例中查找, 找到了則直接使用; 找不到則, 再去實例對象的__proto__指向的原型對象prototype中找, 找到了則使用, 找不到則報錯。 <!DOCTYPE html> <html lang="en"> <head> ...
  • 原型的簡單的語法 構造函數,通過原型添加方法,以下語法,手動修改構造器的指向 實例化對象,並初始化,調用方法 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>title</title> <script> fu ...
  • 什麼樣子的數據是需要寫在原型中? 需要共用的數據就可以寫原型中 原型的作用之一: 數據共用 //屬性需要共用, 方法也需要共用 //不需要共用的數據寫在構造函數中,需要共用的數據寫在原型中 //構造函數 function Student(name,age,sex) { this.name=name; ...
  • 在ant design 的form組件中 能用於提交的組件比較少,所以我在這寫了一個可以單選、多選標簽提交的組件,調用非常簡單。 代碼: 1 import React,{Fragment} from 'react'; 2 import { Tag,Icon,Input } from 'antd'; ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...