」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 事件循環

事件循環

發佈於2024-08-23
瀏覽:497

Event Loop

介绍
JavaScript 主要在 Node.js 和浏览器中的单个线程上执行(有一些例外,例如工作线程,这超出了当前文章的范围)。在这篇文章中,我将尝试解释 Node.js 的并发机制,即事件循环。

在开始阅读本文之前,您应该熟悉堆栈及其工作原理,我过去写过这个想法,所以请查看堆栈和堆 — 不要在不了解它们的情况下开始编码 — Moshe Binieli |中等的

介绍图片
示例
我相信通过示例学习是最好的,因此我将从 4 个简单的代码示例开始。我将分析示例,然后深入探讨 Node.js 的架构。

示例1:
控制台.log(1);
控制台.log(2);
控制台.log(3);
// 输出:
// 1
// 2
// 3
这个例子很简单,第一步console.log(1)进入调用堆栈,被执行然后删除,第二步console.log(2)进入调用堆栈,被执行然后删除,以此类推 console.log(3).

示例 1 的调用堆栈可视化
示例2:
控制台.log(1);
setTimeout(函数 foo(){
控制台.log(2);
}, 0);
控制台.log(3);
// 输出:
// 1
// 3
// 2
在这个例子中我们可以看到我们立即运行setTimeout,所以我们期望console.log(2)在console.log(3)之前,但事实并非如此,让我们了解其背后的机制。

基本事件循环架构(稍后我们将深入探讨)

Stack & Heap:查看我关于此的文章(我在本文开头添加了链接)
Web API:它们内置于您的 Web 浏览器中,能够公开来自浏览器和周围计算机环境的数据,并用它执行有用的复杂操作。它们不是 JavaScript 语言本身的一部分,而是构建在核心 JavaScript 语言之上,为您提供在 JavaScript 代码中使用的额外超能力。例如,Geolocation API 提供了一些简单的 JavaScript 构造来检索位置数据,因此您可以说,在 Google 地图上绘制您的位置。在后台,浏览器实际上使用一些复杂的低级代码(例如 C )与设备的 GPS 硬件(或任何可用于确定位置数据的硬件)进行通信,检索位置数据,并将其返回到浏览器环境以使用在你的代码中。但同样,这种复杂性已被 API 抽象化。
事件循环和回调队列:完成Web API执行的函数将被移动到回调队列,这是一个常规队列数据结构,事件循环负责将下一个函数从回调队列中出队并将该函数发送到执行函数的调用堆栈。
执行顺序

当前位于调用堆栈中的所有函数都会被执行,然后它们会从调用堆栈中弹出。
当调用堆栈为空时,所有排队的任务都会一一弹出到调用堆栈中并执行,然后从调用堆栈中弹出。
让我们来理解示例 2

console.log(1)方法被调用并放置在调用堆栈上并被执行。

  1. setTimeout 方法被调用并放置在调用堆栈上并被执行,此执行创建一个对 setTimeout Web Api 的新调用,持续 0 毫秒,当它完成时(立即,或者更准确地说,那么它会最好说“尽快”)Web Api 将调用移至回调队列。

  2. console.log(3)方法被调用并放置在调用堆栈上并被执行。

  3. Event Loop发现Call Stack为空,从Callback Queue中取出“foo”方法放入Call Stack,然后执行console.log(2)。

示例 2 的过程可视化
所以setTimeout(function,delay)中的delay参数并不代表函数执行后的精确时间延迟。它代表等待的最短时间,之后在某个时间点将执行该函数。

示例3:
控制台.log(1);
setTimeout(函数 foo() {
console.log('foo');
}, 3500);
setTimeout(函数 boo() {
console.log(‘嘘’);
}, 1000);
控制台.log(2);
// 输出:
// 1
// 2
// '嘘'
// 'foo'

示例 3 的过程可视化
例4:
控制台.log(1);
setTimeout(函数 foo() {
console.log('foo');
}, 6500);
setTimeout(函数 boo() {
console.log(‘嘘’);
}, 2500);
setTimeout(函数 baz() {
console.log(‘baz’);
}, 0);
for ([‘A’, ‘B’] 的 const 值) {
console.log(值);
}
函数二() {
控制台.log(2);
}
二();
// 输出:
// 1
// 'A'
// 'B'
// 2
// '巴兹'
// '嘘'
// 'foo'

示例 4 的过程可视化
事件循环继续执行任务队列中等待的所有回调。任务队列内部,任务大致分为两类,即微任务和宏任务。

宏任务(任务队列)和微任务
更准确地说,实际上有两种类型的队列。

  1. 宏任务队列(或简称任务队列)。
  2. 微任务队列。

还有一些任务进入宏任务队列和微任务队列,但我将介绍常见的任务。

常见的宏任务有 setTimeout、setInterval 和 setImmediate。
常见的微任务有 process.nextTick 和 Promise 回调。
执行顺序
当前位于调用堆栈中的所有函数都会被执行,然后它们会从调用堆栈中弹出。
当调用堆栈为空时,所有排队的微任务都会一一弹出到调用堆栈中并执行,然后从调用堆栈中弹出。
当调用栈和微任务队列都为空时,所有排队的宏任务都会一一弹出到调用栈中并执行,然后从调用栈中弹出。
例5:
控制台.log(1);
setTimeout(函数 foo() {
console.log('foo');
}, 0);
Promise.resolve()
.then(函数 boo() {
console.log(‘嘘’);
});
控制台.log(2);
// 输出:
// 1
// 2
// '嘘'
// 'foo'
console.log(1) 方法被调用并放置在调用堆栈上并被执行。
SetTimeout 正在执行,console.log(‘foo’) 被移至 SetTimeout Web Api,0 毫秒后移至宏任务队列。
Promise.resolve() 正在被调用,正在被解析,然后 .then() 方法被移动到微任务队列。
console.log(2) 方法被调用并放置在调用堆栈上并被执行。
Event Loop 发现调用堆栈为空,它首先从 Micro-Task 队列中取出任务,即 Promise 任务,将 console.log('boo') 放在调用堆栈上并执行它。
事件循环看到调用堆栈为空,然后看到微任务为空,然后从宏任务队列中取出下一个任务,即 SetTimeout 任务,放入 console.log('foo')在调用堆栈上并执行它。

示例 5 的过程可视化
对事件循环的高级理解
我正在考虑写一篇关于事件循环机制如何工作的底层文章,它本身可以是一篇文章,所以我决定介绍该主题并附上深入解释该主题的良好链接。

事件循环底层解释
当 Node.js 启动时,它会初始化事件循环,处理提供的输入脚本(或放入 REPL),这可能会进行异步 API 调用、调度计时器或调用 process.nextTick(),然后开始处理事件循环。

下图显示了事件循环操作顺序的简化概述。 (每个框将被称为事件循环的一个“阶段”,请查看介绍图片以更好地理解循环。)

事件循环操作顺序的简化概述
每个阶段都有一个要执行的回调的 FIFO 队列(我在这里小心地说,因为根据实现的不同,可能会有其他数据结构)。虽然每个阶段都有其特殊之处,但通常,当事件循环进入给定阶段时,它将执行特定于该阶段的任何操作,然后执行该阶段队列中的回调,直到队列耗尽或达到最大回调数已执行。当队列耗尽或达到回调限制时,事件循环将进入下一阶段,依此类推。

阶段概述
计时器:此阶段执行由 setTimeout() 和 setInterval() 安排的回调。
挂起的回调:执行延迟到下一个循环迭代的 I/O 回调。
空闲,准备:仅内部使用。
Poll:检索新的I/O事件;执行 I/O 相关回调(几乎所有回调,关闭回调、计时器调度的回调和 setImmediate() 除外);节点会在适当的时候阻塞在这里。
检查:此处调用了 setImmediate() 回调。
关闭回调:一些关闭回调,例如socket.on('关闭', ...).
前面的步骤如何适合这里?
因此,前面仅使用“回调队列”,然后使用“宏和微队列”的步骤是关于事件循环如何工作的抽象解释。

还有一件重要的事情要提,事件循环应该在处理宏任务队列中的一个宏任务之后完全处理微任务队列。

第1步:事件循环将循环时间更新为当前执行的当前时间。
第2步:执行微队列。
步骤 3:执行计时器阶段的任务。
第四步:检查微队列中是否有东西,如果有则执行整个微队列。
步骤5:返回步骤3,直到Timers阶段为空。
步骤 6:执行 Pending Callbacks 阶段的任务。
步骤7:检查微队列中是否有东西,如果有则执行整个微队列。
步骤8:返回步骤6,直到Pending Callbacks阶段为空。
然后空闲…微队列…轮询…微队列…检查…微队列…关闭回调然后重新开始。
因此,我很好地概述了事件循环在幕后如何实际工作,有很多遗漏的部分我在这里没有提到,因为实际文档在解释它方面做得很好,我将提供很好的链接文档,我鼓励您花 10-20 分钟来理解它们。

版本聲明 本文轉載於:https://dev.to/atulnagose1499/event-loop-4fck?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 為什麼在 PHP 中重寫方法參數違反了嚴格的標準?
    為什麼在 PHP 中重寫方法參數違反了嚴格的標準?
    在PHP 中重寫方法參數:違反嚴格標準在物件導向程式設計中,里氏替換原則(LSP) 規定:子類型的物件可以替換其父對象,而不改變程式的行為。然而,在 PHP 中,用不同的參數簽名覆蓋方法被認為是違反嚴格標準的。 為什麼這是違規? PHP 是弱型別語言,這表示編譯器無法在編譯時確定變數的確切型別。這表...
    程式設計 發佈於2024-11-06
  • 哪個 PHP 函式庫提供卓越的 SQL 注入防護:PDO 還是 mysql_real_escape_string?
    哪個 PHP 函式庫提供卓越的 SQL 注入防護:PDO 還是 mysql_real_escape_string?
    PDO vs. mysql_real_escape_string:綜合指南查詢轉義對於防止 SQL 注入至關重要。雖然 mysql_real_escape_string 提供了轉義查詢的基本方法,但 PDO 成為了一種具有眾多優點的卓越解決方案。 什麼是 PDO? PHP 資料物件 (PDO) 是一...
    程式設計 發佈於2024-11-06
  • React 入門:初學者的路線圖
    React 入門:初學者的路線圖
    大家好! ? 我剛開始學習 React.js 的旅程。這是一次令人興奮(有時甚至具有挑戰性!)的冒險,我想分享一下幫助我開始的步驟,以防您也開始研究 React。這是我的處理方法: 1.掌握 JavaScript 基礎 在開始使用 React 之前,我確保溫習一下我的 JavaScript 技能,...
    程式設計 發佈於2024-11-06
  • 如何引用 JavaScript 物件中的內部值?
    如何引用 JavaScript 物件中的內部值?
    如何在JavaScript 物件中引用內部值在JavaScript 中,存取引用同一物件中其他值的物件中的值有時可能具有挑戰性。考慮以下程式碼片段:var obj = { key1: "it ", key2: key1 " works!" }; a...
    程式設計 發佈於2024-11-06
  • Python 列表方法快速指南及範例
    Python 列表方法快速指南及範例
    介紹 Python 清單用途廣泛,並附帶各種內建方法,有助於有效地操作和處理資料。以下是所有主要清單方法的快速參考以及簡短的範例。 1. 追加(項目) 將項目新增至清單末端。 lst = [1, 2, 3] lst.append(4) # [1, 2, 3, ...
    程式設計 發佈於2024-11-06
  • C++ 中何時需要使用者定義的複製建構函式?
    C++ 中何時需要使用者定義的複製建構函式?
    何時需要使用者定義的複製建構子? 複製建構子是 C 物件導向程式設計的組成部分,提供了一種基於現有實例初始化物件的方法。雖然編譯器通常會為類別產生預設的複製建構函數,但在某些情況下需要進行自訂。 需要使用者定義複製建構子的情況當預設複製建構子不夠時,程式設計師會選擇使用者定義的複製建構子來實作自訂複...
    程式設計 發佈於2024-11-06
  • 試...捕捉 V/s 安全分配 (?=):現代發展的福音還是詛咒?
    試...捕捉 V/s 安全分配 (?=):現代發展的福音還是詛咒?
    最近,我發現了 JavaScript 中引入的新安全賦值運算子 (?.=),我對它的簡單性著迷。 ? 安全賦值運算子 (SAO) 是傳統 try...catch 區塊的簡寫替代方案。它允許您內聯捕獲錯誤,而無需為每個操作編寫明確的錯誤處理程式碼。這是一個例子: const [error, resp...
    程式設計 發佈於2024-11-06
  • 如何在Python中優化固定寬度檔案解析?
    如何在Python中優化固定寬度檔案解析?
    優化固定寬度文件解析為了有效地解析固定寬度文件,可以考慮利用Python的struct模組。此方法利用 C 來提高速度,如下例所示:import struct fieldwidths = (2, -10, 24) fmtstring = ' '.join('{}{}'.format(abs(fw),...
    程式設計 發佈於2024-11-06
  • 蠅量級
    蠅量級
    結構模式之一旨在透過與相似物件共享盡可能多的資料來減少記憶體使用。 在處理大量相似物件時特別有用,為每個物件建立一個新實例在記憶體消耗方面會非常昂貴。 關鍵概念: 內在狀態:多個物件之間共享的狀態獨立於上下文,並且在不同物件之間保持相同。 外部狀態:每個物件唯一的、從客戶端傳遞的狀態。此狀態可...
    程式設計 發佈於2024-11-06
  • 解鎖您的 MySQL 掌握:MySQL 實作實驗室課程
    解鎖您的 MySQL 掌握:MySQL 實作實驗室課程
    透過全面的 MySQL 實作實驗室課程提升您的 MySQL 技能並成為資料庫專家。這種實踐學習體驗旨在引導您完成一系列實踐練習,使您能夠克服複雜的 SQL 挑戰並優化資料庫效能。 深入了解 MySQL 無論您是想要建立強大 MySQL 基礎的初學者,還是想要提升專業知識的經驗豐富的...
    程式設計 發佈於2024-11-06
  • 資料夾
    資料夾
    ? ?大家好,我是尼克? ? 利用專家工程解決方案提升您的專案 探索我的產品組合,了解我如何將尖端技術、強大的問題解決能力和創新熱情結合起來,建立可擴展的高效能應用程式。無論您是尋求增強開發流程還是解決複雜的技術挑戰,我都可以幫助您實現願景。看看我的工作,讓我們合作做一些非凡的事情! 在這裡聯絡...
    程式設計 發佈於2024-11-06
  • 透過 Gmail 發送電子郵件時如何修復「SMTP Connect() 失敗」錯誤?
    透過 Gmail 發送電子郵件時如何修復「SMTP Connect() 失敗」錯誤?
    SMTP 連線失敗:解決「SMTP Connect() 失敗」錯誤嘗試使用Gmail 發送電子郵件時,您可能會遇到錯誤訊息指出「SMTP -> 錯誤:無法連線到伺服器:連線逾時(110)\nSMTP Connect()失敗。 要解決此問題,您需要修改負責發送電子郵件的 PHP 程式碼。具體來說,刪除...
    程式設計 發佈於2024-11-06
  • 如何使用 Pillow 在 Python 中水平連接多個映像?
    如何使用 Pillow 在 Python 中水平連接多個映像?
    以Python水平連接影像水平組合多個影像是影像處理中的常見任務。 Python 提供了強大的工具來使用 Pillow 函式庫來實現此目的。 問題描述考慮三個尺寸為 148 x 95 的方形 JPEG 影像。目標是水平連接這些影像影像,同時避免結果輸出中出現任何部分影像。 建議的解決方案以下程式碼片...
    程式設計 發佈於2024-11-06
  • REST API 設計與命名約定指南
    REST API 設計與命名約定指南
    有效地設計RESTful API對於創建可擴展、可維護且易於使用的系統至關重要。雖然存在某些標準,但許多標準並不是嚴格的規則,而是指導 API 設計的最佳實踐。一種廣泛使用的 API 架構模式是 MVC(模型-視圖-控制器),但它本身並不能解決 API 設計的更精細方面,例如命名和結構。在本文中,我...
    程式設計 發佈於2024-11-06

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3