JavaScript運行機制、v8原理、js事件循環(huán)過程
一、有了解過JavaScript引擎嗎?
JavaScript運行機制有沒有詳細了解過?請詳細說明
1. JavaScript是解釋型語言還是編譯型語言?
1.1 編譯型 vs. 解釋型:核心差異
我們可以把編程語言想象成一份外文食譜,為了讓計算機(只會二進制)讀懂,我們需要不同的翻譯方式:
編譯型語言 (Compiled)
- 過程:在程序運行之前,先由編譯器 (Compiler) 將整個源代碼一次性翻譯成機器語言(如 Windows 下的
.exe)。 - 代表:C、C++、Go、Rust。
特點:
- 運行快:運行時無需翻譯,直接執(zhí)行機器碼。
- 不靈活:即使改動一行代碼,也需要重新編譯整個程序。
- 平臺依賴:在 Windows 上編譯的程序通常無法直接在 Linux 上運行。
解釋型語言 (Interpreted)
- 過程:程序運行時,由解釋器 (Interpreter) 一行一行地讀取源代碼,“翻譯”一行就“執(zhí)行”一行。
- 代表:Python、Ruby、早期的 JavaScript。
特點:
- 運行慢:邊譯邊跑,翻譯過程會占用實際運行時間。
- 靈活:跨平臺性好,只要系統(tǒng)安裝了對應的解釋器,代碼即可運行。
1.2 JavaScript 屬于哪一種?
結(jié)論:現(xiàn)代 JavaScript 是一種采用 JIT (Just-In-Time) 即時編譯技術的動態(tài)語言。
雖然傳統(tǒng)上 JS 被視為“解釋型腳本語言”,但現(xiàn)代 JS 引擎(如 Chrome 的 V8)為了極致的性能,早已進化為混合模式。它不再是單純地逐行翻譯,而是通過 JIT 技術在運行過程中動態(tài)優(yōu)化代碼。
1.3 什么是 JIT(即時編譯)?
JIT 結(jié)合了編譯型和解釋型的優(yōu)點,旨在解決“解釋器太慢”和“編譯器啟動久”的痛點。
JIT 的工作流程:
- 快速響應:代碼加載時,解釋器首先介入,快速開始執(zhí)行,讓用戶感知不到延遲。
- 熱點探測:在運行過程中,引擎會監(jiān)控哪些代碼塊被頻繁執(zhí)行(稱為 Hot Spot / 熱點代碼)。
- 即時編譯:JIT 編譯器將這些熱點代碼直接編譯為高效的機器碼。
- 替換執(zhí)行:當再次遇到相同代碼時,直接調(diào)用編譯好的機器碼,跳過解釋步驟。
性能 ≈ 解釋器的響應速度 + 編譯器的執(zhí)行效率 性能 \approx 解釋器的響應速度 + 編譯器的執(zhí)行效率 性能≈解釋器的響應速度+編譯器的執(zhí)行效率
1.4 核心特性對比表
| 特性 | 編譯型 (Compiled) | 解釋型 (Interpreted) | JIT (現(xiàn)代 JS / JVM) |
|---|---|---|---|
| 翻譯時機 | 程序運行前 | 運行時 (逐行翻譯) | 運行時 (按需編譯) |
| 執(zhí)行速度 | 極快 | 較慢 | 接近原生速度 |
| 啟動速度 | 慢 (需等待編譯完成) | 快 | 快 |
| 跨平臺性 | 較低 (需重新編譯) | 極高 | 高 |
| 典型代表 | C++, Rust, Go | Python, PHP | JavaScript (V8), Java |
進階知識:
現(xiàn)代 JS 引擎甚至擁有 “去優(yōu)化 (Deoptimization)” 機制。如果 JIT 編譯器根據(jù)之前的運行數(shù)據(jù)做出了錯誤的優(yōu)化假設(例如本以為某個變量總是數(shù)字,結(jié)果突然變成了字符串),引擎會立即丟棄已優(yōu)化的機器碼,回退到解釋器模式,以確保程序的正確性。
2. 深度解析:JavaScript 引擎 (JS Engine)
2.1 什么是 JavaScript 引擎?
JavaScript 引擎是一個專門負責解析、解釋并執(zhí)行 JavaScript 代碼的程序。
如果把瀏覽器比作一輛汽車,那么 JavaScript 引擎就是這輛車的發(fā)動機。它的核心任務是:將人類可讀的高級代碼(JS)轉(zhuǎn)換為計算機 CPU 能夠理解并運行的二進制機器指令。
2.2 主流引擎概覽
不同的環(huán)境和瀏覽器使用不同的引擎,但它們都遵循 ECMAScript 標準:
| 引擎名稱 | 開發(fā)者 | 主要應用環(huán)境 |
|---|---|---|
| V8 | Chrome, Node.js, Electron, Edge | |
| SpiderMonkey | Mozilla | Firefox |
| JavaScriptCore | Apple | Safari, iOS 全線應用 |
| Chakra | Microsoft | 早期 Edge, IE (已逐步退出舞臺) |
2.3 引擎內(nèi)部是如何工作的? (以 V8 為例)
現(xiàn)代引擎的工作流程并不是簡單的“翻譯”,而是一個復雜的流水線:
解析 (Parsing):
- 引擎將源碼拆解為 Tokens (記號)。
- 生成 AST (Abstract Syntax Tree, 抽象語法樹),這是代碼的結(jié)構(gòu)化表示。
解釋 (Interpretation):
- 解釋器(V8 中的 Ignition)將 AST 轉(zhuǎn)換為中間形態(tài)的 字節(jié)碼 (Bytecode) 并開始執(zhí)行。這一步保證了代碼能以最快速度啟動。
編譯與優(yōu)化 (JIT Compilation):
- 引擎會監(jiān)控運行狀態(tài)。如果某段代碼運行非常頻繁(Hot Spot / 熱點代碼),編譯器(V8 中的 TurboFan)會將其直接編譯為高性能的機器碼。
垃圾回收 (Garbage Collection):
- 引擎內(nèi)置管理機制,自動識別并釋放不再使用的內(nèi)存空間。
2.4 為什么要理解引擎原理?
- 性能優(yōu)化:了解引擎如何識別“熱點代碼”,可以避免寫出觸發(fā)“去優(yōu)化 (Deoptimization)”的代碼(例如頻繁改變對象屬性結(jié)構(gòu))。
- 內(nèi)存管理:理解引擎如何分配內(nèi)存,能更好地規(guī)避內(nèi)存泄漏問題。
- 底層視野:這是從“調(diào)包俠”邁向“架構(gòu)師/高級工程師”的必經(jīng)之路,也是技術面試(尤其是字節(jié)、騰訊等大廠)的??純?nèi)容。
核心要點:
JavaScript 引擎并不是獨立的,它運行在宿主環(huán)境(如瀏覽器或 Node.js)中。引擎只負責執(zhí)行 JS,而 DOM 操作、網(wǎng)絡請求(AJAX)、定時器(setTimeout)是由宿主環(huán)境提供的 Web APIs 或內(nèi)置模塊處理的。
3. 深度解析:瀏覽器引擎 (Browser Engine / Rendering Engine)
3.1 什么是瀏覽器引擎?
如果說 JS 引擎 是汽車的“發(fā)動機”,那么 瀏覽器引擎(也稱渲染引擎)就是整臺車的“底盤與組裝車間”。
它的核心職責是:讀取 HTML、CSS 和圖像資源,經(jīng)過一系列復雜的計算,最終將網(wǎng)頁內(nèi)容像素化并繪制在用戶的屏幕上。
3.2 三足鼎立:主流引擎分布
目前市面上絕大多數(shù)瀏覽器都基于以下三大引擎構(gòu)建:
| 引擎名稱 | 主要開發(fā)者 | 代表瀏覽器 | 特點 |
|---|---|---|---|
| Blink | Google / 社區(qū) | Chrome, Edge, Opera | WebKit 的分支,目前生態(tài)位最強,性能優(yōu)異。 |
| WebKit | Apple | Safari, 所有 iOS 瀏覽器 | 注重能效比,是蘋果生態(tài)系統(tǒng)的唯一準入引擎。 |
| Gecko | Mozilla | Firefox | 堅持獨立開發(fā),高度尊重隱私與 Web 標準。 |
3.3 渲染流水線 (Rendering Pipeline)
瀏覽器引擎將代碼轉(zhuǎn)化為圖像的過程被稱為 關鍵渲染路徑 (Critical Rendering Path):
解析 (Parsing):
- 解析 HTML → 生成 DOM 樹。
- 解析 CSS → 生成 CSSOM 樹。
構(gòu)建渲染樹 (Render Tree):
- 將 DOM 與 CSSOM 合并。引擎會過濾掉不需要顯示的元素(如
display: none)。
布局 (Layout / Reflow):
- 計算每個節(jié)點在屏幕上的確切幾何位置(高、寬、坐標)。
繪制 (Painting):
- 將渲染樹中的每個節(jié)點轉(zhuǎn)換成屏幕上的實際像素點。
合成 (Compositing):
- 將網(wǎng)頁的各個圖層(Layers)按正確順序疊加,生成最終圖像。
3.4 瀏覽器引擎 vs. JS 引擎 的協(xié)作
兩者雖各司其職,但在運行過程中緊密配合:
- 渲染中斷:當瀏覽器引擎解析 HTML 遇到
<script>標簽時,會暫停渲染,等待 JS 引擎 執(zhí)行完腳本。 - 雙向通信:JS 引擎通過 DOM API 修改網(wǎng)頁內(nèi)容,瀏覽器引擎接收到指令后觸發(fā) 重排(Reflow) 或 重繪(Repaint)。
3.5 開發(fā)者為何必須掌握它?
- 性能調(diào)優(yōu):理解布局(Layout)比繪制(Paint)更耗性能,可以減少不必要的頁面卡頓。
- 解決兼容性:了解不同引擎對 CSS 特性的實現(xiàn)差異(如
-webkit-前綴的由來)。 - 面試深度:它是大廠面試題“從輸入 URL 到頁面顯示發(fā)生了什么”的核心環(huán)節(jié)。
黃金公式:
瀏覽器 (Browser) = 瀏覽器引擎 (Blink/WebKit) + JS 引擎 (V8/JSC) + 網(wǎng)絡模塊 + UI 界面 + 各類 Web API。
4. 深度解析:V8 引擎如何執(zhí)行 JavaScript 代碼
4.1 解析階段 (Parsing)
當 V8 接收到源碼字符串后,會進行兩步預處理:
詞法分析 (Scanner):
詞法分析是解析的第一步,它的核心目標是:“識字”并“切分”。
切分單詞(Tokenizing):將一連串的源碼字符流拆分成一個個具有獨立語義的單元,稱為 Token(詞法單元)。
- 例如:let a = 10; 會被拆分為 let (關鍵字), a (變量名), = (賦值符), 10 (數(shù)字), ; (分隔符)。
過濾雜質(zhì):自動剔除代碼中對邏輯運行無意義的內(nèi)容,如空格、換行符、注釋等。
初步分類與轉(zhuǎn)換:將字符串轉(zhuǎn)換為內(nèi)部編號(ID)。對于引擎來說,處理數(shù)字 1(代表關鍵字 let)比處理字符串 “let” 要快得多。
詞法錯誤檢查:發(fā)現(xiàn)不符合詞法規(guī)則的字符。例如在 JS 中寫了一個非法的特殊符號,詞法分析階段就會報錯。
語法分析 (Parser):
語法分析是解析的第二步,它的核心目標是:“組句”并“建樹”。
構(gòu)建 AST(抽象語法樹):將詞法分析產(chǎn)出的平鋪的 Token 序列,根據(jù)語言的語法規(guī)則(文法)組裝成一棵樹狀結(jié)構(gòu)。這棵樹展示了代碼之間的層級和邏輯關系。
- 例如:它會識別出 a = 10 是一個“賦值表達式”,其中 a 是左值,10 是右值。
驗證語法合法性:檢查 Token 的排列順序是否符合 JS 語法。
- 詞法分析能認出 let、=、;,但只有語法分析能告訴你 let = ; 是錯誤的排布。
確定作用域與語義:在建樹的過程中,解析器會初步確定變量的作用域(全局還是局部),并為后續(xù)生成字節(jié)碼提供邏輯依據(jù)。
4.2 解釋階段 (Interpretation)
- 角色:Ignition 解釋器。
- 動作:將 AST 轉(zhuǎn)換為 Bytecode (字節(jié)碼) 并立即開始執(zhí)行。
- 優(yōu)勢:字節(jié)碼生成速度極快,且比機器碼占用更少的內(nèi)存,保證了網(wǎng)頁的“首屏加載速度”。
4.3 監(jiān)控與分析 (Profiling)
- 在代碼運行期間,V8 會啟動一個 Profiler 監(jiān)聽運行狀態(tài)。
- 它會尋找那些被多次調(diào)用的函數(shù)或循環(huán),將其標記為 “Hot Spot” (熱點代碼)。
4.4 優(yōu)化編譯 (JIT Compilation)
- 角色:TurboFan 編譯器。
- 動作:將“熱點代碼”的字節(jié)碼直接編譯為 Optimized Machine Code (優(yōu)化機器碼)。
- 結(jié)果:機器碼是二進制指令,CPU 直接讀取執(zhí)行,運行速度接近 C++ 原生水平。
4.5 去優(yōu)化 (Deoptimization)
- 原理:JS 是動態(tài)類型語言。如果 TurboFan 假設某個變量一直是數(shù)字并進行了優(yōu)化,但運行中它突然變成了字符串。
- 處理:引擎會立即撤銷優(yōu)化(Deopt),回退到 Ignition 解釋器執(zhí)行字節(jié)碼,確保邏輯正確性。
總結(jié):V8 的執(zhí)行流水線
| 狀態(tài) | 處理過程 | 產(chǎn)物 | 特點 |
|---|---|---|---|
| 初始 | 源碼讀取 | 字符串 | 人類可讀 |
| 分析 | Parsing | AST 樹 | 邏輯結(jié)構(gòu)化 |
| 啟動 | Ignition | 字節(jié)碼 | 響應快、內(nèi)存省 |
| 加速 | TurboFan | 機器碼 | 執(zhí)行快、性能高 |
開發(fā)者啟示:
了解這個過程后,你會發(fā)現(xiàn):保持變量類型的一致性(不要隨意改變對象屬性的類型或結(jié)構(gòu))能顯著減少“去優(yōu)化”的發(fā)生,讓代碼始終運行在 TurboFan 的“高速公路”上。
5 JS 引擎的運行機制與環(huán)境隔離
5.1 為什么“解釋型”語言也需要先“掃描”代碼?
雖然 JavaScript 是即時編譯(JIT)語言,但它在執(zhí)行前必須經(jīng)過 Parser(解析器) 的全量掃描。
- 語法安全檢查:在代碼運行前發(fā)現(xiàn)
SyntaxError(如括號不匹配),防止程序運行到一半崩潰,保證執(zhí)行的原子性。 - 構(gòu)建 AST (抽象語法樹):將純文本轉(zhuǎn)為機器能理解的邏輯樹,這是后續(xù)生成字節(jié)碼的必備前提。
- 預分配內(nèi)存 (Hoisting):在掃描階段,引擎需要識別出所有的變量聲明,從而在內(nèi)存中提前開辟空間。
5.2 環(huán)境隔離:變量環(huán)境 vs. 詞法環(huán)境
為了在兼容老舊 var 代碼的同時,完美支持 ES6 的 let/const 塊級作用域,V8 將執(zhí)行上下文拆分為兩個獨立的存儲區(qū)域:
變量環(huán)境 (Variable Environment)
- 存放內(nèi)容:
var聲明的變量、函數(shù)聲明。 - 設計目的:維護傳統(tǒng)的函數(shù)作用域。
- 底層行為:在創(chuàng)建階段,變量會被初始化為
undefined(產(chǎn)生變量提升現(xiàn)象)。
詞法環(huán)境 (Lexical Environment)
- 存放內(nèi)容:
let、const聲明的變量、with語句、try...catch。 - 設計目的:支持塊級作用域。
- 底層行為:在創(chuàng)建階段,變量僅被記錄名稱,不進行初始化(產(chǎn)生暫時性死區(qū) TDZ)。
5.3 “物理隔離”帶來的深遠影響
這種設計決定了 JavaScript 在運行時的三個核心表現(xiàn):
查找順序:
當訪問一個變量時,引擎優(yōu)先查找當前上下文的詞法環(huán)境,若無,再查找變量環(huán)境,最后順著作用域鏈向上尋找。這保證了塊級變量優(yōu)先于函數(shù)級變量。
塊級作用域的實現(xiàn):
在執(zhí)行過程中,每進入一個 {} 塊,詞法環(huán)境都會創(chuàng)建一個小型環(huán)境棧(Stack),并在退出塊時將其銷毀。這解決了 var 變量容易污染全局或循環(huán)體的問題。
暫時性死區(qū) (TDZ):
由于詞法環(huán)境中的變量在聲明前處于“未初始化”狀態(tài),任何提前訪問都會觸發(fā)錯誤。這迫使開發(fā)者養(yǎng)成“先聲明后使用”的良好習慣。
核心總結(jié)表
| 特性 | 變量環(huán)境 (var) | 詞法環(huán)境 (let/const) |
|---|---|---|
| 提升行為 | 提升聲明并初始化為 undefined | 提升聲明但不初始化 |
| 作用域單位 | 函數(shù) (Function Scope) | 塊 ({}) (Block Scope) |
| 重復聲明 | 允許 | 禁止 |
| 訪問限制 | 自由訪問(可能拿到 undefined) | 嚴格限制(TDZ 報錯) |
底層思考:
這種“雙環(huán)境”設計是現(xiàn)代 JS 引擎為了兼顧歷史兼容性與現(xiàn)代語言特性而做出的工程妥協(xié),也是其性能與靈活性并存的秘密武器。
二、JavaScript 是單線程還是多線程?
請問異步任務處理機制是怎么樣的?分別說明瀏覽器與 Node 的事件循環(huán)機制
1. 核心定性:JavaScript 到底是不是單線程?
結(jié)論:JavaScript 語言執(zhí)行是單線程的,但其運行宿主環(huán)境(瀏覽器/Node.js)是多線程的。
- 為什么單線程? 主要為了避免復雜的 DOM 操作沖突(如:線程 A 刪除節(jié)點,線程 B 修改節(jié)點)。
- 如何處理異步? JS 引擎遇到異步任務(定時器、網(wǎng)絡請求)時,會將其交給瀏覽器的其他線程(渲染線程、HTTP 線程、定時器線程、事件觸發(fā)線程(處理點擊、滾動事件))處理。處理完成后,回調(diào)函數(shù)會進入“任務隊列”等待執(zhí)行。
2. 異步任務全家桶 (全面清單)
異步任務根據(jù)執(zhí)行優(yōu)先級的不同,分為 宏任務 (Macrotask) 和 微任務 (Microtask)。
微任務 (Microtask) —— 優(yōu)先級最高
執(zhí)行時機:當前調(diào)用棧清空后,立即執(zhí)行,且必須清空整個微任務隊列,才會進行下一次渲染或執(zhí)行宏任務。
Promise.then()/catch()/finally()async / await(本質(zhì)是 Promise 的語法糖)process.nextTick(Node.js 特有,微任務中的“王者”,優(yōu)先級高于 Promise)MutationObserver(瀏覽器端,監(jiān)聽 DOM 變化)queueMicrotask()(手動開啟微任務的官方 API)
宏任務 (Macrotask) —— 優(yōu)先級次之
執(zhí)行時機:由宿主環(huán)境發(fā)起。每輪事件循環(huán)只取出一個宏任務執(zhí)行。
script(整體代碼塊,是第一個宏任務)setTimeout/setIntervalsetImmediate(Node.js 特有)I/O操作 (文件讀寫、網(wǎng)絡請求回調(diào)、數(shù)據(jù)庫操作)UI Rendering(瀏覽器特有,每輪循環(huán)結(jié)束后視情況觸發(fā))postMessage/MessageChannel
3. 事件循環(huán) (Event Loop) 執(zhí)行模型
瀏覽器的執(zhí)行順序
- 執(zhí)行同步代碼(屬于第一個宏任務)。
- 同步代碼執(zhí)行完,檢查并清空整個微任務隊列。
- (視情況) 進行 UI 渲染。
- 從宏任務隊列中取入一個任務執(zhí)行。
- 回到步驟 2,循環(huán)往復。
Node.js 的執(zhí)行順序 (libuv)
Node.js 10+ 后與瀏覽器基本一致,但其底層分為 6 個階段循環(huán):
- timers:執(zhí)行
setTimeout等回調(diào)。 - pending callbacks:執(zhí)行某些系統(tǒng)操作的回調(diào)。
- idle, prepare:內(nèi)部使用。
- poll (輪詢):處理 I/O 回調(diào),這是最核心階段。
- check:執(zhí)行
setImmediate的回調(diào)。 - close callbacks:執(zhí)行關閉回調(diào)(如
socket.on('close'))。
4. 高頻面試避坑指南 (Killer Points)
Q1:Promise 內(nèi)部是異步的嗎?
坑點:new Promise((resolve) => { ... }) 括號里的代碼是同步執(zhí)行的!只有 .then() 里面的回調(diào)才是異步微任務。
Q2:await 后面代碼的執(zhí)行順序?
坑點:await 這一行右邊的表達式會立即執(zhí)行。而 await 下方的代碼會被阻塞,并存入微任務隊列(相當于 .then)。
黃金總結(jié):
一個宏任務 → \rightarrow → 所有微任務 → \rightarrow → 渲染 → \rightarrow → 下一個宏任務。
5. 異步任務調(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í)行同步代碼(第一個宏任務)
此時,代碼從上到下掃一遍,同步代碼直接進入 調(diào)用棧 執(zhí)行,異步任務分發(fā)到各自隊列。
- 第 1 行:打印 stack [1]。
- 第 2, 3 行:遇到 setTimeout,將 macro [2] 和 macro [3] 分發(fā)到 宏任務隊列。
- 第 6-15 行:循環(huán) 3 次。p.then 是異步的,將三個 then 回調(diào)依次放入 微任務隊列(標記為 micro A, B, C)。
- 第 17 行:打印 stack [8]。
當前狀態(tài):
- 控制臺輸出:stack [1] → \rightarrow → stack [8]
- 微任務隊列:[micro A, micro B, micro C]
- 宏任務隊列:[macro [2], macro [3]]
5.2 第二輪:清空微任務隊列(核心環(huán)節(jié))
步代碼跑完,調(diào)用??樟?,事件循環(huán)立即去清空所有的微任務。
執(zhí)行 micro A:
- 內(nèi)部同步代碼:打印 stack [7](循環(huán)第 1 次)。
- 內(nèi)部異步:遇到 setTimeout,將 stack [4] 的那個回調(diào)推入 宏任務隊列。
執(zhí)行 micro B:
- 內(nèi)部同步代碼:打印 stack [7](循環(huán)第 2 次)。
- 內(nèi)部異步:又將一個 stack [4] 推入 宏任務隊列。
執(zhí)行 micro C:內(nèi)部同步代碼:
- 打印 stack [7](循環(huán)第 3 次)。
- 內(nèi)部異步:再將一個 stack [4] 推入 宏任務隊列。
當前狀態(tài):
- 控制臺輸出:…stack [8] → \rightarrow → stack [7] → \rightarrow → stack [7] → \rightarrow → stack [7]
- 微任務隊列:空
- 宏任務隊列:[macro [2], macro [3], stack [4]-A, stack [4]-B, stack [4]-C]
5.3 第三輪:開始執(zhí)行宏任務
微任務清空后,事件循環(huán)取宏任務隊列中的 第一個 任務出來執(zhí)行。
- 執(zhí)行 macro [2]:打印 macro [2]。
- 執(zhí)行 macro [3]:打印 macro [3]。
執(zhí)行第一個 stack [4]-A:
- 同步代碼:打印 stack [4]。
- 異步嵌套1:setTimeout,將 macro [5] 推入宏任務隊列末尾。
- 異步嵌套2:p.then,將 micro [6] 推入 微任務隊列。
注意! 宏任務執(zhí)行完,會立即檢查并清空微任務隊列。所以此時會先打印 micro [6],再跑下一個宏任務。
以此類推,執(zhí)行完 B 和 C 組。
5.4 最終輸出順序結(jié)果
為了方便你核對,最終的打印順序如下:
- stack [1]
- stack [8]
- stack [7] (循環(huán)1次)
- stack [7] (循環(huán)2次)
- stack [7] (循環(huán)3次)
- macro [2]
- macro [3]
- stack [4] (A組)
- micro [6] (A組微任務優(yōu)先執(zhí)行)
- stack [4] (B組)
- micro [6] (B組微任務優(yōu)先執(zhí)行)
- stack [4] (C組)
- micro [6] (C組微任務優(yōu)先執(zhí)行)
- macro [5] (A組嵌套)
- macro [5] (B組嵌套)
- macro [5] (C組嵌套)
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
微信小程序?qū)崿F(xiàn)自定義動畫彈框/提示框的方法實例
這篇文章主要給大家介紹了關于微信小程序?qū)崿F(xiàn)自定義動畫彈框/提示框的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11
登陸成功后自動計算秒數(shù)執(zhí)行跳轉(zhuǎn)
本文實現(xiàn)的是這樣的一個功能登陸成功后自動查秒跳轉(zhuǎn),具體示例如下,不了解的朋友可以學習下哦2014-01-01
微信小程序項目總結(jié)之記賬小程序功能的實現(xiàn)(包括后端)
這篇文章主要介紹了微信小程序項目總結(jié)之記賬小程序功能的實現(xiàn)方法(包括后端),需要的朋友可以參考下2019-08-08

