最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript運(yùn)行機(jī)制、v8原理、js事件循環(huán)過(guò)程

 更新時(shí)間:2026年06月06日 09:55:04   作者:Hello--_--World  
了解JavaScript引擎與運(yùn)行機(jī)制,包括解釋型與編譯型語(yǔ)言的區(qū)別,JIT技術(shù)如何提升性能,以及JavaScript的單線程特性與異步任務(wù)處理機(jī)制,通過(guò)解析、解釋、監(jiān)控與優(yōu)化等步驟,現(xiàn)代JavaScript引擎如如V8,實(shí)現(xiàn)了高效的代碼執(zhí)行

一、有了解過(guò)JavaScript引擎嗎?

JavaScript運(yùn)行機(jī)制有沒(méi)有詳細(xì)了解過(guò)?請(qǐng)?jiān)敿?xì)說(shuō)明

1. JavaScript是解釋型語(yǔ)言還是編譯型語(yǔ)言?

1.1 編譯型 vs. 解釋型:核心差異

我們可以把編程語(yǔ)言想象成一份外文食譜,為了讓計(jì)算機(jī)(只會(huì)二進(jìn)制)讀懂,我們需要不同的翻譯方式:

編譯型語(yǔ)言 (Compiled)

  • 過(guò)程:在程序運(yùn)行之前,先由編譯器 (Compiler) 將整個(gè)源代碼一次性翻譯成機(jī)器語(yǔ)言(如 Windows 下的 .exe)。
  • 代表:C、C++、Go、Rust。

特點(diǎn)

  • 運(yùn)行快:運(yùn)行時(shí)無(wú)需翻譯,直接執(zhí)行機(jī)器碼。
  • 不靈活:即使改動(dòng)一行代碼,也需要重新編譯整個(gè)程序。
  • 平臺(tái)依賴:在 Windows 上編譯的程序通常無(wú)法直接在 Linux 上運(yùn)行。

解釋型語(yǔ)言 (Interpreted)

  • 過(guò)程:程序運(yùn)行時(shí),由解釋器 (Interpreter) 一行一行地讀取源代碼,“翻譯”一行就“執(zhí)行”一行。
  • 代表:Python、Ruby、早期的 JavaScript。

特點(diǎn)

  • 運(yùn)行慢:邊譯邊跑,翻譯過(guò)程會(huì)占用實(shí)際運(yùn)行時(shí)間。
  • 靈活:跨平臺(tái)性好,只要系統(tǒng)安裝了對(duì)應(yīng)的解釋器,代碼即可運(yùn)行。

1.2 JavaScript 屬于哪一種?

結(jié)論:現(xiàn)代 JavaScript 是一種采用 JIT (Just-In-Time) 即時(shí)編譯技術(shù)的動(dòng)態(tài)語(yǔ)言。

雖然傳統(tǒng)上 JS 被視為“解釋型腳本語(yǔ)言”,但現(xiàn)代 JS 引擎(如 Chrome 的 V8)為了極致的性能,早已進(jìn)化為混合模式。它不再是單純地逐行翻譯,而是通過(guò) JIT 技術(shù)在運(yùn)行過(guò)程中動(dòng)態(tài)優(yōu)化代碼。

1.3 什么是 JIT(即時(shí)編譯)?

JIT 結(jié)合了編譯型和解釋型的優(yōu)點(diǎn),旨在解決“解釋器太慢”和“編譯器啟動(dòng)久”的痛點(diǎn)。

JIT 的工作流程:

  1. 快速響應(yīng):代碼加載時(shí),解釋器首先介入,快速開(kāi)始執(zhí)行,讓用戶感知不到延遲。
  2. 熱點(diǎn)探測(cè):在運(yùn)行過(guò)程中,引擎會(huì)監(jiān)控哪些代碼塊被頻繁執(zhí)行(稱為 Hot Spot / 熱點(diǎn)代碼)。
  3. 即時(shí)編譯JIT 編譯器將這些熱點(diǎn)代碼直接編譯為高效的機(jī)器碼。
  4. 替換執(zhí)行:當(dāng)再次遇到相同代碼時(shí),直接調(diào)用編譯好的機(jī)器碼,跳過(guò)解釋步驟。

性能 ≈ 解釋器的響應(yīng)速度 + 編譯器的執(zhí)行效率 性能 \approx 解釋器的響應(yīng)速度 + 編譯器的執(zhí)行效率 性能解釋器的響應(yīng)速度+編譯器的執(zhí)行效率

1.4 核心特性對(duì)比表

特性編譯型 (Compiled)解釋型 (Interpreted)JIT (現(xiàn)代 JS / JVM)
翻譯時(shí)機(jī)程序運(yùn)行前運(yùn)行時(shí) (逐行翻譯)運(yùn)行時(shí) (按需編譯)
執(zhí)行速度極快較慢接近原生速度
啟動(dòng)速度慢 (需等待編譯完成)
跨平臺(tái)性較低 (需重新編譯)極高
典型代表C++, Rust, GoPython, PHPJavaScript (V8), Java

進(jìn)階知識(shí):

現(xiàn)代 JS 引擎甚至擁有 “去優(yōu)化 (Deoptimization)” 機(jī)制。如果 JIT 編譯器根據(jù)之前的運(yùn)行數(shù)據(jù)做出了錯(cuò)誤的優(yōu)化假設(shè)(例如本以為某個(gè)變量總是數(shù)字,結(jié)果突然變成了字符串),引擎會(huì)立即丟棄已優(yōu)化的機(jī)器碼,回退到解釋器模式,以確保程序的正確性。

2. 深度解析:JavaScript 引擎 (JS Engine)

2.1 什么是 JavaScript 引擎?

JavaScript 引擎是一個(gè)專(zhuān)門(mén)負(fù)責(zé)解析、解釋并執(zhí)行 JavaScript 代碼的程序。

如果把瀏覽器比作一輛汽車(chē),那么 JavaScript 引擎就是這輛車(chē)的發(fā)動(dòng)機(jī)。它的核心任務(wù)是:將人類(lèi)可讀的高級(jí)代碼(JS)轉(zhuǎn)換為計(jì)算機(jī) CPU 能夠理解并運(yùn)行的二進(jìn)制機(jī)器指令。

2.2 主流引擎概覽

不同的環(huán)境和瀏覽器使用不同的引擎,但它們都遵循 ECMAScript 標(biāo)準(zhǔn):

引擎名稱開(kāi)發(fā)者主要應(yīng)用環(huán)境
V8GoogleChrome, Node.js, Electron, Edge
SpiderMonkeyMozillaFirefox
JavaScriptCoreAppleSafari, iOS 全線應(yīng)用
ChakraMicrosoft早期 Edge, IE (已逐步退出舞臺(tái))

2.3 引擎內(nèi)部是如何工作的? (以 V8 為例)

現(xiàn)代引擎的工作流程并不是簡(jiǎn)單的“翻譯”,而是一個(gè)復(fù)雜的流水線:

解析 (Parsing)

  • 引擎將源碼拆解為 Tokens (記號(hào))。
  • 生成 AST (Abstract Syntax Tree, 抽象語(yǔ)法樹(shù)),這是代碼的結(jié)構(gòu)化表示。

解釋 (Interpretation)

  • 解釋器(V8 中的 Ignition)將 AST 轉(zhuǎn)換為中間形態(tài)的 字節(jié)碼 (Bytecode) 并開(kāi)始執(zhí)行。這一步保證了代碼能以最快速度啟動(dòng)。

編譯與優(yōu)化 (JIT Compilation)

  • 引擎會(huì)監(jiān)控運(yùn)行狀態(tài)。如果某段代碼運(yùn)行非常頻繁(Hot Spot / 熱點(diǎn)代碼),編譯器(V8 中的 TurboFan)會(huì)將其直接編譯為高性能的機(jī)器碼。

垃圾回收 (Garbage Collection)

  • 引擎內(nèi)置管理機(jī)制,自動(dòng)識(shí)別并釋放不再使用的內(nèi)存空間。

2.4 為什么要理解引擎原理?

  • 性能優(yōu)化:了解引擎如何識(shí)別“熱點(diǎn)代碼”,可以避免寫(xiě)出觸發(fā)“去優(yōu)化 (Deoptimization)”的代碼(例如頻繁改變對(duì)象屬性結(jié)構(gòu))。
  • 內(nèi)存管理:理解引擎如何分配內(nèi)存,能更好地規(guī)避內(nèi)存泄漏問(wèn)題。
  • 底層視野:這是從“調(diào)包俠”邁向“架構(gòu)師/高級(jí)工程師”的必經(jīng)之路,也是技術(shù)面試(尤其是字節(jié)、騰訊等大廠)的??純?nèi)容。

核心要點(diǎn):

JavaScript 引擎并不是獨(dú)立的,它運(yùn)行在宿主環(huán)境(如瀏覽器或 Node.js)中。引擎只負(fù)責(zé)執(zhí)行 JS,而 DOM 操作、網(wǎng)絡(luò)請(qǐng)求(AJAX)、定時(shí)器(setTimeout)是由宿主環(huán)境提供的 Web APIs 或內(nèi)置模塊處理的。

3. 深度解析:瀏覽器引擎 (Browser Engine / Rendering Engine)

3.1 什么是瀏覽器引擎?

如果說(shuō) JS 引擎 是汽車(chē)的“發(fā)動(dòng)機(jī)”,那么 瀏覽器引擎(也稱渲染引擎)就是整臺(tái)車(chē)的“底盤(pán)與組裝車(chē)間”。

它的核心職責(zé)是:讀取 HTML、CSS 和圖像資源,經(jīng)過(guò)一系列復(fù)雜的計(jì)算,最終將網(wǎng)頁(yè)內(nèi)容像素化并繪制在用戶的屏幕上。

3.2 三足鼎立:主流引擎分布

目前市面上絕大多數(shù)瀏覽器都基于以下三大引擎構(gòu)建:

引擎名稱主要開(kāi)發(fā)者代表瀏覽器特點(diǎn)
BlinkGoogle / 社區(qū)Chrome, Edge, OperaWebKit 的分支,目前生態(tài)位最強(qiáng),性能優(yōu)異。
WebKitAppleSafari, 所有 iOS 瀏覽器注重能效比,是蘋(píng)果生態(tài)系統(tǒng)的唯一準(zhǔn)入引擎。
GeckoMozillaFirefox堅(jiān)持獨(dú)立開(kāi)發(fā),高度尊重隱私與 Web 標(biāo)準(zhǔn)。

3.3 渲染流水線 (Rendering Pipeline)

瀏覽器引擎將代碼轉(zhuǎn)化為圖像的過(guò)程被稱為 關(guān)鍵渲染路徑 (Critical Rendering Path)

解析 (Parsing)

  • 解析 HTML → 生成 DOM 樹(shù)
  • 解析 CSS → 生成 CSSOM 樹(shù)。

構(gòu)建渲染樹(shù) (Render Tree)

  • 將 DOM 與 CSSOM 合并。引擎會(huì)過(guò)濾掉不需要顯示的元素(如 display: none)。

布局 (Layout / Reflow)

  • 計(jì)算每個(gè)節(jié)點(diǎn)在屏幕上的確切幾何位置(高、寬、坐標(biāo))。

繪制 (Painting)

  • 將渲染樹(shù)中的每個(gè)節(jié)點(diǎn)轉(zhuǎn)換成屏幕上的實(shí)際像素點(diǎn)。

合成 (Compositing)

  • 將網(wǎng)頁(yè)的各個(gè)圖層(Layers)按正確順序疊加,生成最終圖像。

3.4 瀏覽器引擎 vs. JS 引擎 的協(xié)作

兩者雖各司其職,但在運(yùn)行過(guò)程中緊密配合:

  • 渲染中斷:當(dāng)瀏覽器引擎解析 HTML 遇到 <script> 標(biāo)簽時(shí),會(huì)暫停渲染,等待 JS 引擎 執(zhí)行完腳本。
  • 雙向通信:JS 引擎通過(guò) DOM API 修改網(wǎng)頁(yè)內(nèi)容,瀏覽器引擎接收到指令后觸發(fā) 重排(Reflow)重繪(Repaint)

3.5 開(kāi)發(fā)者為何必須掌握它?

  • 性能調(diào)優(yōu):理解布局(Layout)比繪制(Paint)更耗性能,可以減少不必要的頁(yè)面卡頓。
  • 解決兼容性:了解不同引擎對(duì) CSS 特性的實(shí)現(xiàn)差異(如 -webkit- 前綴的由來(lái))。
  • 面試深度:它是大廠面試題“從輸入 URL 到頁(yè)面顯示發(fā)生了什么”的核心環(huán)節(jié)。

黃金公式:

瀏覽器 (Browser) = 瀏覽器引擎 (Blink/WebKit) + JS 引擎 (V8/JSC) + 網(wǎng)絡(luò)模塊 + UI 界面 + 各類(lèi) Web API。

4. 深度解析:V8 引擎如何執(zhí)行 JavaScript 代碼

4.1 解析階段 (Parsing)

當(dāng) V8 接收到源碼字符串后,會(huì)進(jìn)行兩步預(yù)處理:

詞法分析 (Scanner):

詞法分析是解析的第一步,它的核心目標(biāo)是:“識(shí)字”并“切分”。

切分單詞(Tokenizing):將一連串的源碼字符流拆分成一個(gè)個(gè)具有獨(dú)立語(yǔ)義的單元,稱為 Token(詞法單元)。

  • 例如:let a = 10; 會(huì)被拆分為 let (關(guān)鍵字), a (變量名), = (賦值符), 10 (數(shù)字), ; (分隔符)。

過(guò)濾雜質(zhì):自動(dòng)剔除代碼中對(duì)邏輯運(yùn)行無(wú)意義的內(nèi)容,如空格、換行符、注釋等。

初步分類(lèi)與轉(zhuǎn)換:將字符串轉(zhuǎn)換為內(nèi)部編號(hào)(ID)。對(duì)于引擎來(lái)說(shuō),處理數(shù)字 1(代表關(guān)鍵字 let)比處理字符串 “let” 要快得多。

詞法錯(cuò)誤檢查:發(fā)現(xiàn)不符合詞法規(guī)則的字符。例如在 JS 中寫(xiě)了一個(gè)非法的特殊符號(hào),詞法分析階段就會(huì)報(bào)錯(cuò)。

語(yǔ)法分析 (Parser):

語(yǔ)法分析是解析的第二步,它的核心目標(biāo)是:“組句”并“建樹(shù)”。

構(gòu)建 AST(抽象語(yǔ)法樹(shù)):將詞法分析產(chǎn)出的平鋪的 Token 序列,根據(jù)語(yǔ)言的語(yǔ)法規(guī)則(文法)組裝成一棵樹(shù)狀結(jié)構(gòu)。這棵樹(shù)展示了代碼之間的層級(jí)和邏輯關(guān)系。

  • 例如:它會(huì)識(shí)別出 a = 10 是一個(gè)“賦值表達(dá)式”,其中 a 是左值,10 是右值。

驗(yàn)證語(yǔ)法合法性:檢查 Token 的排列順序是否符合 JS 語(yǔ)法。

  • 詞法分析能認(rèn)出 let、=、;,但只有語(yǔ)法分析能告訴你 let = ; 是錯(cuò)誤的排布。

確定作用域與語(yǔ)義:在建樹(shù)的過(guò)程中,解析器會(huì)初步確定變量的作用域(全局還是局部),并為后續(xù)生成字節(jié)碼提供邏輯依據(jù)。

4.2 解釋階段 (Interpretation)

  • 角色Ignition 解釋器
  • 動(dòng)作:將 AST 轉(zhuǎn)換為 Bytecode (字節(jié)碼) 并立即開(kāi)始執(zhí)行。
  • 優(yōu)勢(shì):字節(jié)碼生成速度極快,且比機(jī)器碼占用更少的內(nèi)存,保證了網(wǎng)頁(yè)的“首屏加載速度”。

4.3 監(jiān)控與分析 (Profiling)

  • 在代碼運(yùn)行期間,V8 會(huì)啟動(dòng)一個(gè) Profiler 監(jiān)聽(tīng)運(yùn)行狀態(tài)。
  • 它會(huì)尋找那些被多次調(diào)用的函數(shù)或循環(huán),將其標(biāo)記為 “Hot Spot” (熱點(diǎn)代碼)

4.4 優(yōu)化編譯 (JIT Compilation)

  • 角色TurboFan 編譯器。
  • 動(dòng)作:將“熱點(diǎn)代碼”的字節(jié)碼直接編譯為 Optimized Machine Code (優(yōu)化機(jī)器碼)
  • 結(jié)果:機(jī)器碼是二進(jìn)制指令,CPU 直接讀取執(zhí)行,運(yùn)行速度接近 C++ 原生水平。

4.5 去優(yōu)化 (Deoptimization)

  • 原理:JS 是動(dòng)態(tài)類(lèi)型語(yǔ)言。如果 TurboFan 假設(shè)某個(gè)變量一直是數(shù)字并進(jìn)行了優(yōu)化,但運(yùn)行中它突然變成了字符串。
  • 處理:引擎會(huì)立即撤銷(xiāo)優(yōu)化(Deopt),回退到 Ignition 解釋器執(zhí)行字節(jié)碼,確保邏輯正確性。

總結(jié):V8 的執(zhí)行流水線

狀態(tài)處理過(guò)程產(chǎn)物特點(diǎn)
初始源碼讀取字符串人類(lèi)可讀
分析ParsingAST 樹(shù)邏輯結(jié)構(gòu)化
啟動(dòng)Ignition字節(jié)碼響應(yīng)快、內(nèi)存省
加速TurboFan機(jī)器碼執(zhí)行快、性能高

開(kāi)發(fā)者啟示

了解這個(gè)過(guò)程后,你會(huì)發(fā)現(xiàn):保持變量類(lèi)型的一致性(不要隨意改變對(duì)象屬性的類(lèi)型或結(jié)構(gòu))能顯著減少“去優(yōu)化”的發(fā)生,讓代碼始終運(yùn)行在 TurboFan 的“高速公路”上。

5 JS 引擎的運(yùn)行機(jī)制與環(huán)境隔離

5.1 為什么“解釋型”語(yǔ)言也需要先“掃描”代碼?

雖然 JavaScript 是即時(shí)編譯(JIT)語(yǔ)言,但它在執(zhí)行前必須經(jīng)過(guò) Parser(解析器) 的全量掃描。

  • 語(yǔ)法安全檢查:在代碼運(yùn)行前發(fā)現(xiàn) SyntaxError(如括號(hào)不匹配),防止程序運(yùn)行到一半崩潰,保證執(zhí)行的原子性。
  • 構(gòu)建 AST (抽象語(yǔ)法樹(shù)):將純文本轉(zhuǎn)為機(jī)器能理解的邏輯樹(shù),這是后續(xù)生成字節(jié)碼的必備前提。
  • 預(yù)分配內(nèi)存 (Hoisting):在掃描階段,引擎需要識(shí)別出所有的變量聲明,從而在內(nèi)存中提前開(kāi)辟空間。

5.2 環(huán)境隔離:變量環(huán)境 vs. 詞法環(huán)境

為了在兼容老舊 var 代碼的同時(shí),完美支持 ES6 的 let/const 塊級(jí)作用域,V8 將執(zhí)行上下文拆分為兩個(gè)獨(dú)立的存儲(chǔ)區(qū)域:

變量環(huán)境 (Variable Environment)

  • 存放內(nèi)容var 聲明的變量、函數(shù)聲明。
  • 設(shè)計(jì)目的:維護(hù)傳統(tǒng)的函數(shù)作用域。
  • 底層行為:在創(chuàng)建階段,變量會(huì)被初始化為 undefined(產(chǎn)生變量提升現(xiàn)象)。

詞法環(huán)境 (Lexical Environment)

  • 存放內(nèi)容let、const 聲明的變量、with 語(yǔ)句、try...catch。
  • 設(shè)計(jì)目的:支持塊級(jí)作用域
  • 底層行為:在創(chuàng)建階段,變量?jī)H被記錄名稱,不進(jìn)行初始化(產(chǎn)生暫時(shí)性死區(qū) TDZ)。

5.3 “物理隔離”帶來(lái)的深遠(yuǎn)影響

這種設(shè)計(jì)決定了 JavaScript 在運(yùn)行時(shí)的三個(gè)核心表現(xiàn):

查找順序

當(dāng)訪問(wèn)一個(gè)變量時(shí),引擎優(yōu)先查找當(dāng)前上下文的詞法環(huán)境,若無(wú),再查找變量環(huán)境,最后順著作用域鏈向上尋找。這保證了塊級(jí)變量?jī)?yōu)先于函數(shù)級(jí)變量。

塊級(jí)作用域的實(shí)現(xiàn)

在執(zhí)行過(guò)程中,每進(jìn)入一個(gè) {} 塊,詞法環(huán)境都會(huì)創(chuàng)建一個(gè)小型環(huán)境棧(Stack),并在退出塊時(shí)將其銷(xiāo)毀。這解決了 var 變量容易污染全局或循環(huán)體的問(wèn)題。

暫時(shí)性死區(qū) (TDZ)

由于詞法環(huán)境中的變量在聲明前處于“未初始化”狀態(tài),任何提前訪問(wèn)都會(huì)觸發(fā)錯(cuò)誤。這迫使開(kāi)發(fā)者養(yǎng)成“先聲明后使用”的良好習(xí)慣。

核心總結(jié)表

特性變量環(huán)境 (var)詞法環(huán)境 (let/const)
提升行為提升聲明并初始化為 undefined提升聲明但不初始化
作用域單位函數(shù) (Function Scope)塊 ({}) (Block Scope)
重復(fù)聲明允許禁止
訪問(wèn)限制自由訪問(wèn)(可能拿到 undefined)嚴(yán)格限制(TDZ 報(bào)錯(cuò))

底層思考

這種“雙環(huán)境”設(shè)計(jì)是現(xiàn)代 JS 引擎為了兼顧歷史兼容性現(xiàn)代語(yǔ)言特性而做出的工程妥協(xié),也是其性能與靈活性并存的秘密武器。

二、JavaScript 是單線程還是多線程?

請(qǐng)問(wèn)異步任務(wù)處理機(jī)制是怎么樣的?分別說(shuō)明瀏覽器與 Node 的事件循環(huán)機(jī)制

1. 核心定性:JavaScript 到底是不是單線程?

結(jié)論:JavaScript 語(yǔ)言執(zhí)行是單線程的,但其運(yùn)行宿主環(huán)境(瀏覽器/Node.js)是多線程的。

  • 為什么單線程? 主要為了避免復(fù)雜的 DOM 操作沖突(如:線程 A 刪除節(jié)點(diǎn),線程 B 修改節(jié)點(diǎn))。
  • 如何處理異步? JS 引擎遇到異步任務(wù)(定時(shí)器、網(wǎng)絡(luò)請(qǐng)求)時(shí),會(huì)將其交給瀏覽器的其他線程(渲染線程、HTTP 線程、定時(shí)器線程、事件觸發(fā)線程(處理點(diǎn)擊、滾動(dòng)事件))處理。處理完成后,回調(diào)函數(shù)會(huì)進(jìn)入“任務(wù)隊(duì)列”等待執(zhí)行。

2. 異步任務(wù)全家桶 (全面清單)

異步任務(wù)根據(jù)執(zhí)行優(yōu)先級(jí)的不同,分為 宏任務(wù) (Macrotask)微任務(wù) (Microtask)

微任務(wù) (Microtask) —— 優(yōu)先級(jí)最高

執(zhí)行時(shí)機(jī):當(dāng)前調(diào)用棧清空后,立即執(zhí)行,且必須清空整個(gè)微任務(wù)隊(duì)列,才會(huì)進(jìn)行下一次渲染或執(zhí)行宏任務(wù)。

  • Promise.then() / catch() / finally()
  • async / await (本質(zhì)是 Promise 的語(yǔ)法糖)
  • process.nextTick (Node.js 特有,微任務(wù)中的“王者”,優(yōu)先級(jí)高于 Promise)
  • MutationObserver (瀏覽器端,監(jiān)聽(tīng) DOM 變化)
  • queueMicrotask() (手動(dòng)開(kāi)啟微任務(wù)的官方 API)

宏任務(wù) (Macrotask) —— 優(yōu)先級(jí)次之

執(zhí)行時(shí)機(jī):由宿主環(huán)境發(fā)起。每輪事件循環(huán)只取出一個(gè)宏任務(wù)執(zhí)行。

  • script (整體代碼塊,是第一個(gè)宏任務(wù))
  • setTimeout / setInterval
  • setImmediate (Node.js 特有)
  • I/O 操作 (文件讀寫(xiě)、網(wǎng)絡(luò)請(qǐng)求回調(diào)、數(shù)據(jù)庫(kù)操作)
  • UI Rendering (瀏覽器特有,每輪循環(huán)結(jié)束后視情況觸發(fā))
  • postMessage / MessageChannel

3. 事件循環(huán) (Event Loop) 執(zhí)行模型

瀏覽器的執(zhí)行順序

  1. 執(zhí)行同步代碼(屬于第一個(gè)宏任務(wù))。
  2. 同步代碼執(zhí)行完,檢查并清空整個(gè)微任務(wù)隊(duì)列
  3. (視情況) 進(jìn)行 UI 渲染。
  4. 從宏任務(wù)隊(duì)列中取入一個(gè)任務(wù)執(zhí)行。
  5. 回到步驟 2,循環(huán)往復(fù)。

Node.js 的執(zhí)行順序 (libuv)

Node.js 10+ 后與瀏覽器基本一致,但其底層分為 6 個(gè)階段循環(huán):

  1. timers:執(zhí)行 setTimeout 等回調(diào)。
  2. pending callbacks:執(zhí)行某些系統(tǒng)操作的回調(diào)。
  3. idle, prepare:內(nèi)部使用。
  4. poll (輪詢):處理 I/O 回調(diào),這是最核心階段。
  5. check:執(zhí)行 setImmediate 的回調(diào)。
  6. close callbacks:執(zhí)行關(guān)閉回調(diào)(如 socket.on('close'))。

4. 高頻面試避坑指南 (Killer Points)

Q1:Promise 內(nèi)部是異步的嗎?

坑點(diǎn)new Promise((resolve) => { ... }) 括號(hào)里的代碼是同步執(zhí)行的!只有 .then() 里面的回調(diào)才是異步微任務(wù)。

Q2:await 后面代碼的執(zhí)行順序?

坑點(diǎn)await 這一行右邊的表達(dá)式會(huì)立即執(zhí)行。而 await 下方的代碼會(huì)被阻塞,并存入微任務(wù)隊(duì)列(相當(dāng)于 .then)。

黃金總結(jié)

一個(gè)宏任務(wù) → \rightarrow 所有微任務(wù) → \rightarrow 渲染 → \rightarrow 下一個(gè)宏任務(wù)。

5. 異步任務(wù)調(diào)度題

// 作業(yè)題 console.log('stack [1]');
console.log('stack [1]'); 

setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);

const p = Promise.resolve();
for(let i = 0; i < 3; i++) {
    p.then(() => {
        setTimeout(() => {
            console.log('stack [4]')
            setTimeout(() => console.log("macro [5]"), 0);
            p.then(() => console.log('micro [6]'));
        }, 0);
        console.log("stack [7]");
    });
}

console.log("stack [8]"); 

5.1 第一輪:執(zhí)行同步代碼(第一個(gè)宏任務(wù))

此時(shí),代碼從上到下掃一遍,同步代碼直接進(jìn)入 調(diào)用棧 執(zhí)行,異步任務(wù)分發(fā)到各自隊(duì)列。

  • 第 1 行:打印 stack [1]。
  • 第 2, 3 行:遇到 setTimeout,將 macro [2] 和 macro [3] 分發(fā)到 宏任務(wù)隊(duì)列。
  • 第 6-15 行:循環(huán) 3 次。p.then 是異步的,將三個(gè) then 回調(diào)依次放入 微任務(wù)隊(duì)列(標(biāo)記為 micro A, B, C)。
  • 第 17 行:打印 stack [8]。

當(dāng)前狀態(tài):

  • 控制臺(tái)輸出:stack [1] → \rightarrow stack [8]
  • 微任務(wù)隊(duì)列:[micro A, micro B, micro C]
  • 宏任務(wù)隊(duì)列:[macro [2], macro [3]]

5.2 第二輪:清空微任務(wù)隊(duì)列(核心環(huán)節(jié))

步代碼跑完,調(diào)用??樟耍录h(huán)立即去清空所有的微任務(wù)。

執(zhí)行 micro A:

  • 內(nèi)部同步代碼:打印 stack [7](循環(huán)第 1 次)。
  • 內(nèi)部異步:遇到 setTimeout,將 stack [4] 的那個(gè)回調(diào)推入 宏任務(wù)隊(duì)列。

執(zhí)行 micro B:

  • 內(nèi)部同步代碼:打印 stack [7](循環(huán)第 2 次)。
  • 內(nèi)部異步:又將一個(gè) stack [4] 推入 宏任務(wù)隊(duì)列。

執(zhí)行 micro C:內(nèi)部同步代碼:

  • 打印 stack [7](循環(huán)第 3 次)。
  • 內(nèi)部異步:再將一個(gè) stack [4] 推入 宏任務(wù)隊(duì)列。

當(dāng)前狀態(tài):

  • 控制臺(tái)輸出:…stack [8] → \rightarrow stack [7] → \rightarrow stack [7] → \rightarrow stack [7]
  • 微任務(wù)隊(duì)列:空
  • 宏任務(wù)隊(duì)列:[macro [2], macro [3], stack [4]-A, stack [4]-B, stack [4]-C]

5.3 第三輪:開(kāi)始執(zhí)行宏任務(wù)

微任務(wù)清空后,事件循環(huán)取宏任務(wù)隊(duì)列中的 第一個(gè) 任務(wù)出來(lái)執(zhí)行。

  • 執(zhí)行 macro [2]:打印 macro [2]。
  • 執(zhí)行 macro [3]:打印 macro [3]。

執(zhí)行第一個(gè) stack [4]-A:

  • 同步代碼:打印 stack [4]。
  • 異步嵌套1:setTimeout,將 macro [5] 推入宏任務(wù)隊(duì)列末尾。
  • 異步嵌套2:p.then,將 micro [6] 推入 微任務(wù)隊(duì)列。

注意! 宏任務(wù)執(zhí)行完,會(huì)立即檢查并清空微任務(wù)隊(duì)列。所以此時(shí)會(huì)先打印 micro [6],再跑下一個(gè)宏任務(wù)。

以此類(lèi)推,執(zhí)行完 B 和 C 組。

5.4 最終輸出順序結(jié)果

為了方便你核對(duì),最終的打印順序如下:

  1. stack [1]
  2. stack [8]
  3. stack [7] (循環(huán)1次)
  4. stack [7] (循環(huán)2次)
  5. stack [7] (循環(huán)3次)
  6. macro [2]
  7. macro [3]
  8. stack [4] (A組)
  9. micro [6] (A組微任務(wù)優(yōu)先執(zhí)行)
  10. stack [4] (B組)
  11. micro [6] (B組微任務(wù)優(yōu)先執(zhí)行)
  12. stack [4] (C組)
  13. micro [6] (C組微任務(wù)優(yōu)先執(zhí)行)
  14. macro [5] (A組嵌套)
  15. macro [5] (B組嵌套)
  16. macro [5] (C組嵌套)

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論

博罗县| 河源市| 当涂县| 五原县| 济宁市| 镇坪县| 唐海县| 莒南县| 上蔡县| 安多县| 阳高县| 江阴市| 建昌县| 昔阳县| 芮城县| 盈江县| 青龙| 崇信县| 德昌县| 宁津县| 安平县| 胶南市| 彰武县| 和顺县| 佳木斯市| 新巴尔虎左旗| 梁山县| 团风县| 巴东县| 科技| 太白县| 常州市| 陆河县| 信丰县| 西青区| 丁青县| 板桥市| 涿州市| 莱西市| 辉县市| 佛冈县|