JavaScript 源碼剖析:從字節碼到執行的奇妙旅程

JavaScript,這門風靡全球的腳本語言,以其靈活性和跨平臺性征服了無數開發者。我們每天都在使用它,但它在后臺是如何工作的?一段看似簡單的JS代碼,在執行之前究竟經歷了哪些“變形記”?

今天,讓我們一起踏上一段奇妙的旅程,深入剖析JavaScript引擎(以V8引擎為例)的源碼,了解我們的代碼是如何一步步從文本形式,轉化為機器可以理解并執行的指令的。我們將重點關注從源碼到字節碼,再到機器碼的轉換過程。

一、 JavaScript 引擎:代碼的“煉金爐”

我們編寫的JavaScript代碼,本質上是文本。而計算機硬件只能理解機器碼(0和1的序列)。因此,JavaScript引擎就扮演了關鍵的角色,它負責將我們寫的JS代碼,翻譯成計算機可以執行的低級指令。

市面上最流行的JavaScript引擎莫過于Google V8引擎(用于Chrome瀏覽器和Node.js)。V8是開源的,這意味著我們可以一窺其內部運作的奧秘。

V8引擎的工作流程大致可以分為以下幾個階段:

解析 (Parsing): 將JavaScript源代碼轉換為抽象語法樹 (Abstract Syntax Tree, AST)。

編譯 (Compilation):

Ignition (解釋器): 將AST轉換為字節碼 (Bytecode)。

TurboFan (優化編譯器): 基于字節碼和執行過程中的數據,生成高度優化的機器碼。

執行 (Execution):

解釋器執行字節碼。

JIT (Just-In-Time) 編譯器生成機器碼,并替換掉部分字節碼,使部分代碼執行更快。

1. 源碼到AST:理解代碼的結構

當JavaScript引擎接收到源代碼時,第一步是詞法分析 (Lexical Analysis) 和語法分析 (Syntax Analysis)。

詞法分析 (Lexical Analysis / Tokenizing): 引擎會讀取源代碼,將其分解成一系列有意義的“詞法單元”(Tokens)。例如,let x = 10; 會被分解成 let (關鍵字), x (標識符), = (賦值運算符), 10 (數字字面量), ; (語句結束符)。

語法分析 (Syntax Analysis / Parsing): 引擎接收這些Tokens,并根據JavaScript的語法規則,構建一個抽象語法樹 (Abstract Syntax Tree, AST)。AST是一個樹狀的數據結構,它直觀地表示了代碼的結構和語法關系,而不包含具體的語法細節(如分號、括號等)。

示例:

假設有如下JavaScript代碼:

<JAVASCRIPT>

function add(a, b) {

return a + b;

}

let result = add(5, 3);

經過解析后,可能會生成一個類似以下的AST(簡化表示):

<TEXT>

Program

├── FunctionDeclaration: add

│ ├── Identifier: a

│ ├── Identifier: b

│ └── BlockStatement

│ └── ReturnStatement

│ └── BinaryExpression: +

│ ├── Identifier: a

│ └── Identifier: b

└── VariableDeclaration: result (let)

└── AssignmentExpression: =

└── CallExpression: add

├── Identifier: add

├── Literal: 5

└── Literal: 3

AST是后續編譯過程的重要輸入。

2. AST到字節碼:Ignition 解釋器的作用

雖然像C++這樣的語言有編譯器直接將源碼編譯成機器碼,但JavaScript由于其動態性(如動態類型、動態添加屬性等),直接生成機器碼的成本很高,且優化空間有限。

V8引擎采用了解釋與編譯結合 (Hybrid approach) 的策略:

Ignition (解釋器): 負責將AST轉換為字節碼 (Bytecode)。字節碼是一種中間表示形式,它比AST更接近機器碼,但又比機器碼更抽象,并且比直接解釋AST更有效率。

Sparkplug (快速發生器): V8還有一個名為Sparkplug的快速發生器,它直接將AST編譯成機器碼,用于快速啟動執行。

TurboFan (優化編譯器): 當代碼被執行多次(熱代碼),并且積累了足夠的類型信息(例如,某個函數總是接收數字類型的參數),TurboFan就會介入,對這部分“熱代碼”進行深度優化,生成高度優化的本地機器碼。

為什么需要字節碼?

性能提升: 解釋器逐條執行字節碼,比直接解析AST要快得多。

內存節省: 字節碼通常比AST更緊湊,占用的內存更少。

優化基礎: 字節碼包含了執行過程中所需的類型信息和執行流信息,為TurboFan進行性能優化打下了基礎。

跨平臺性: 字節碼本身是平臺無關的,后續的機器碼生成則依賴于目標平臺。

字節碼的結構:

字節碼由一系列的操作碼 (Opcodes) 組成,每個操作碼代表一個具體的指令,例如加載變量、執行算術運算、調用函數等。Ignition解釋器會逐一讀取這些操作碼并執行相應的操作。

示例(假設):

我們來看一段簡單的加法操作,如果使用字節碼表示,可能看起來像這樣(這是一個高度簡化的概念性示例):

<JAVASCRIPT>

let a = 5;

let b = 3;

let sum = a + b;

轉換成字節碼(概念性):

地址

操作碼 (Opcode)

操作數 (Operand)

描述

0x00

LdarA

0x01

加載局部變量 a(索引0x01)到寄存器

0x02

Star

0x03

a 的值存入某個臨時存儲位置(寄存器)

0x04

LdarB

0x02

加載局部變量 b(索引0x02)到寄存器

0x06

Star

0x04

b 的值存入某個臨時存儲位置(寄存器)

0x08

Add

執行加法操作,結果存入一個新寄存器

0x09

StaResult

0x03

將加法結果存入局部變量 result

LdarA (Load Register a): 將變量a的值加載到CPU的一個寄存器中。

Star (Store): 將寄存器的值存儲到某個位置。

Add: 執行加法操作。

StaResult (Store to result): 將結果存入變量result。

Ignition解釋器會逐條讀取這些字節碼,并調用底層的C++代碼來執行相應的操作。

3. 字節碼到機器碼:TurboFan 的優化魔術

雖然解釋器可以執行字節碼,但解釋執行通常比直接運行機器碼慢。為了提高性能,V8引擎引入了即時編譯 (Just-In-Time, JIT) 技術,其中 TurboFan 扮演了核心角色。

熱代碼檢測 (Hot Code Detection): Ignition在執行字節碼時,會記錄每個函數的執行次數、參數類型等信息。如果一個函數被執行的次數達到一定閾值(成為“熱代碼”),并且其參數類型穩定(例如,總是接收數字),V8就會觸發TurboFan進行優化編譯。

類型反饋 (Type Feedback): TurboFan 會利用 Ignition 收集到的類型信息來做出更智能的優化決策。例如,如果一個 + 操作符之前總是處理數字,TurboFan 就可以生成只處理數字加法的最優機器碼。如果遇到其他類型(如字符串拼接),它會回退到解釋執行,或者重新編譯。

窺孔優化 (Peephole Optimization): TurboFan 會檢查一小段連續的字節碼,并尋找可以優化的地方(例如,將多個簡單指令合并成一個更高效的指令)。

內聯 (Inlining): 將小函數的函數調用直接替換為函數體本身的代碼,避免函數調用的開銷。

逃逸分析 (Escape Analysis): 確定一個對象的生命周期是否超出其創建的函數的范圍。如果一個對象沒有“逃逸”出去,V8就可以將其分配在棧上,而不是堆上,從而提高效率。

示例:

考慮這段代碼:

<JAVASCRIPT>

function addNumbers(a, b) {

return a + b;

}

let x = 10, y = 20;

addNumbers(x, y); // 第一次執行

addNumbers(x, y); // 第二次執行

// ...

addNumbers(x, y); // 第1000次執行

Ignition 解釋執行: 首次執行時,Ignition 會將 addNumbers 函數的AST轉換為字節碼,并開始解釋執行。它會記錄addNumbers被調用了1000次,并且參數a和b都是數字類型。

TurboFan 介入: 當調用次數達到閾值時,TurboFan 會介入。它接收addNumbers函數相關的字節碼和類型反饋信息,并生成高度優化的機器碼,例如:

<ASSEMBLY>

; TurboFan生成的針對數字加法的機器碼 (AMD64示例)

mov rax, rdi ; 將參數a (在rdi寄存器中) 移動到rax

add rax, rsi ; 將參數b (在rsi寄存器中) 加到rax

ret ; 返回結果 (在rax中)

這個機器碼直接執行數字加法,無需經過Ignition解釋器。

字節碼與機器碼的替換: V8會將這部分熱代碼的執行路徑從解釋執行字節碼,切換到直接執行TurboFan生成的機器碼。當addNumbers再次被調用時,JS引擎會直接執行這段高效的機器碼。

4. 氫氧化機碼:執行的最終形態

實際上,V8引擎并不僅僅是生成機器碼,它還可能進行一些臨時的“中間代碼”生成,甚至直接生成機器碼。

V8的現代流水線一般是:

AST -> Sparkplug -> 機器碼 (快速啟動)

AST -> Ignition -> 字節碼 -> Ignition 解釋執行 (收集信息)

根據信息 -> TurboFan (優化編譯器) -> 高度優化的機器碼 (熱代碼)

這種多層級的編譯與優化策略,使得JavaScript在提供動態性的同時,也能在關鍵路徑上達到接近靜態語言的性能。

二、 深入理解關鍵機制

1. 變量環境與作用域鏈 (Variable Environment & Scope Chain)

JavaScript 的變量和作用域是通過執行上下文 (Execution Context) 來管理的。每個函數調用都會創建一個新的執行上下文,其中包含:

變量環境 (Variable Environment): 存儲了函數聲明、變量聲明(let, const, var)和函數參數。

詞法環境 (Lexical Environment):

環境記錄 (Environment Record): 存儲了當前作用域中的標識符(變量、函數)。

外部環境的引用 (Outer Environment Reference): 指向其父級作用域的詞法環境。

正是通過這個詞法環境的鏈接(即作用域鏈),JavaScript才能解析變量的訪問。當查找一個變量時,引擎會沿著作用域鏈向上查找,直到找到該變量或到達全局作用域。

2. 閉包 (Closures) 的幕后

通過詞法環境的鏈式結構,我們就能理解閉包是如何工作的了。當一個內部函數被返回給外部時,它會捕獲其被創建時的詞法環境。即使外部函數已經執行完畢,內部函數仍然可以訪問其捕獲的變量。

<JAVASCRIPT>

function outer() {

let outerVar = "I'm from outer";

function inner() {

// inner 函數捕獲了 outer 的詞法環境,包括 outerVar

console.log(outerVar);

}

return inner;

}

let myInnerFunc = outer(); // outer 執行完畢,但 outerVar 仍然被 myInnerFunc 引用

myInnerFunc(); // 輸出: I'm from outer

這里的 inner 函數以及它所引用的 outerVar 共同構成了閉包。

3. 內存管理與閉包

閉包雖然強大,但也可能導致內存問題。如果一個閉包持續持有大量不再需要的變量的引用,這些變量就無法被垃圾回收。

<JAVASCRIPT>

function createLargeObject() {

let largeArray = new Array(1000000).fill('X'); // 1MB of data approximately

return function() {

// 這個內部函數 (閉包) 持有了 largeArray 的引用

// 即使 createLargeObject 已經執行完畢

console.log(largeArray.length); // 每次調用都訪問 largeArray

};

}

let closureExample = createLargeObject();

// closureExample = null; // 只有當 closureExample 本身不再被引用時,largeArray 才可能被回收

當 closureExample(即 createLargeObject 返回的那個函數)被設置為 null 時,才打破了對 largeArray 的引用,largeArray 及其所占用的內存才有可能被垃圾回收。

三、 性能考量:優化

理解上述機制,有助于我們寫出更高效的JavaScript代碼:

避免不必要的全局變量: 強制使用 use strict,并注意作用域。

優化熱代碼: 編寫結構清晰、類型穩定的函數,以便TurboFan進行優化。避免在熱代碼中進行大量的動態類型轉換或動態添加屬性。

謹慎使用閉包: 意識到閉包可能對內存的影響,必要時打破引用,設置 null。

理解迭代器的性能: 例如,在循環中進行大量創建和銷毀對象的操作,可能會給GC帶來壓力。

利用 Map 和 Set: 它們在處理鍵值對和唯一值時,通常比簡單的對象更高效,尤其是在涉及大量數據時。

四、 總結

JavaScript的執行過程是一個精妙的“煉金術”:

源碼 -> 詞法單元 -> AST: 結構化理解代碼。

AST -> 字節碼 ( Ignition ): 生成便于解釋執行的中間格式。

字節碼 -> 機器碼 ( Sparkplug / TurboFan ): 針對熱代碼進行深度優化,實現高性能執行。

V8引擎的這種分層策略,平衡了JavaScript的動態特性和性能需求。理解這個過程,不僅能讓我們深入了解JavaScript的運行機制,也能幫助我們寫出更高效、更可靠的代碼,并在遇到性能問題時,能有方法去定位和解決。

下次當你運行一段JavaScript代碼時,不妨想象一下它正在引擎內部經歷的這場奇妙的轉化之旅!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/95495.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/95495.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/95495.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

FPGA—硬件電路一旦上電配置完成,各個功能模塊會并行地持續工作

1.示例代碼參考這段代碼是用 Verilog 編寫的一個 LED 閃爍控制模塊&#xff0c;主要實現了 LED 按一定時間間隔循環移位閃爍的功能。下面詳細解釋其架構組成&#xff1a;模塊定義與端口聲明模塊名為 led_flash&#xff0c;包含三個端口&#xff1a;sys_clk&#xff1a;輸入端口…

從零到上線:Docker、Docker Compose 與 Runtime 安裝部署全指南(含實戰示例與應用場景)

文章目錄一、Docker 安裝1. Ubuntu / Debian&#xff08;官方倉庫&#xff09;2. RHEL / CentOS / Rocky / AlmaLinux3. 驗證4. macOS / Windows&#xff08;Docker Desktop&#xff09;二、Docker Compose&#xff08;V2&#xff09;安裝與基本用法1) 驗證2) 最小示例&#xf…

Java基礎篇02:基本語法

1 注釋 注釋是寫在程序中對代碼進行解釋說明的文字&#xff0c;方便自己和其他人查看&#xff0c;以便理解程序的。注釋分為三種&#xff1a;單行注釋、多行注釋、文檔注釋注釋不影響代碼的執行&#xff1a; 原因是編譯后的文件已經沒有注釋了// 這是單行注釋&#xff1a;。通常…

【SECS/GEM 】SECS/GEM 日志管理相關的消息

明白 ? 在 SECS/GEM 架構里&#xff0c;設備日志&#xff08;Equipment Logging 主要涉及 事件日志&#xff08;Event Log&#xff09;、報警日志&#xff08;Alarm Log&#xff09;、配方操作日志&#xff08;Recipe Log&#xff09;、以及用戶操作/命令日志。這些日志通過 S…

ragas 框架使用Chat-GLM模型報API 調用參數有誤,請檢查文檔

ragas 框架使用Chat-GLM模型報API 調用參數有誤&#xff0c;請檢查文檔解決方案 from ragas.llms import LangchainLLMWrapper # 點擊LangchainLLMWrapper 進入這個類找到這個方法直接 return 0.1出現問題原因 ChatGLM 不支持設置temperature等于0&#xff0c;默認的值太小了

Kaggle - LLM Science Exam 大模型做科學選擇題

Kaggle - LLM Science Exam Science Exam Simple Approach w/ Model Hub | Kaggle Platypus2-70B with Wikipedia RAG | Kaggle 5個選項只有一個選項正確&#xff0c;目標&#xff1a;回答一個選項序列&#xff08;只有前三個有效&#xff09; 輸出正確選項 &#xff08;可…

貪吃蛇魚小游戲抖音快手微信小程序看廣告流量主開源

核心優勢&#xff1a;為流量主運營者與新手量身打造 1. 為流量主運營者破解成本困局 本地化運行&#xff0c;零服務器成本&#xff1a;數據運行與存儲全程在用戶手機本地完成&#xff0c;無需部署服務器及后臺系統&#xff0c;徹底擺脫服務器租賃、維護等硬性支出&#xff0c;…

PDF Reader 編輯閱讀工具(Mac中文)

原文地址&#xff1a;PDF Reader 編輯閱讀 for Mac v5.2.0 PDF Reader Pro Mac&#xff0c;是一款PDF編輯閱讀&#xff0c;PDF Reader Pro讓您直接在 Mac 上進行PDF文件閱讀、筆記、編輯、轉換、創建PDF、簽署PDFs、填寫PDF Forms表單、設置密碼、合并拆分文件、水印等等&…

Django REST framework:SimpleRouter 使用指南

1. SimpleRouter 是什么&#xff1f; SimpleRouter 是 DRF&#xff08;Django REST framework&#xff09;提供的路由器&#xff0c;能根據 ViewSet 自動生成標準的 REST 路由&#xff0c;包括&#xff1a; GET /resources/ → 列表&#xff08;list&#xff09;POST /resource…

覆蓋Transformer、GAN:掩碼重建正在重塑時間序列領域!

隨著大數據與深度學習的發展&#xff0c;時間序列分析的建模能力顯著提升&#xff0c;而掩碼重建作為一種自監督學習范式&#xff0c;已成為提升序列表征能力的重要技術。該方法通過隨機掩碼部分數據并重建原始序列&#xff0c;迫使模型挖掘時序依賴性與潛在模式&#xff0c;在…

用AI做TikTok影視解說,全流程全自動成片,不懂外語也能做全球矩陣!

多語種解說&#xff1a; 短劇出海狂吸美金 多語種解說搶先機 TikTok、YouTube等平臺&#xff0c;尤其在非英語市場&#xff0c;內容供給仍遠遠不足&#xff0c;每一個小語種市場都是潛在藍海。 有人用英語講仙俠、西語講爽劇、日語講宮斗、阿語講懸疑&#xff0c;一夜漲粉百…

解密大語言模型推理:輸入處理背后的數學與工程實踐

解密大語言模型推理&#xff1a;輸入處理背后的數學與工程實踐當你向ChatGPT提問時&#xff0c;短短幾秒內就能獲得流暢的回答&#xff0c;這背后隱藏著怎樣的技術魔法&#xff1f;答案在于大語言模型高效推理過程中精妙的輸入處理機制。在現代大語言模型推理中&#xff0c;輸入…

02、連接服務器的幾種方式

02、連接服務器的幾種方式 1、Xshell 適用于Windows https://www.xshell.com/en/free-for-home-school/ 2、Termius 適用于MacOS 直接蘋果商店下載即可 3、IDEA 連接 Tools - Deployment - Browse Remote Host 1、打開Browse Remote Host2、添加服務3、輸入服務器連接信息并測試…

高并發系統設計方案(直播場景)

最近在準備面試&#xff0c;正把平時積累的筆記、項目中遇到的問題與解決方案、對核心原理的理解&#xff0c;以及高頻業務場景的應對策略系統梳理一遍&#xff0c;既能加深記憶&#xff0c;也能讓知識體系更扎實&#xff0c;供大家參考&#xff0c;歡迎討論。 1. 微服務拆分 …

網絡編程基礎:一文搞懂 Socket、HTTP、HTTPS、TCP/IP、SSL 的關系

在日常開發中&#xff0c;我們經常聽到 Socket、HTTP、HTTPS、TCP/IP、SSL 這些術語&#xff0c;這些概念往往容易混淆&#xff0c;且讓人感到困惑。本文將用最通俗易懂的方式來講清這些網絡概念及其相互關系。一、從寄信說起&#xff1a;網絡通信的本質假如你要給遠方的朋友寄…

查看LoRA 哪個適配器處于激活狀態(67)

哪個適配器處于激活狀態 當前哪個適配器處于激活狀態?我們來查看active_adapter屬性就知道了 peft_model.active_adapter輸出 default試試另一個(適配器) 你更想試試另一個(適配器)嗎?只需調用set_adapter()方法即可。 peft_model.set_adapter(yoda) peft_model.act…

??Nginx高性能Web服務器實戰:從協議原理到運維優化??

目錄 前言 一、Web基礎概念 1.1 什么是Web&#xff1f; 1.2 B/S架構模型 1.3 Web請求與響應流程 1.4 靜態資源 vs 動態資源 二、HTTP/HTTPS協議詳解 2.1 HTTP與HTTPS區別 2.2 HTTPS握手流程 2.3 HTTP狀態碼大全 三、Nginx核心知識 3.1 Nginx簡介 3.2 Nginx vs Apache 3.3 Nginx…

【先楫HPM5E00_EVK系列-板卡測評3】hpm5e00evk平臺中斷、定時器、PWM、USART等基礎功能詳解

此文介紹了利用先楫半導體&#xff08;hpm&#xff09;官方hpm5e00_evk開發板使用的主控芯片的一些原理性知識&#xff0c;無實驗內容展示&#xff0c;主要匯總了先楫半導體hpm5e00主控芯片的中斷、定時器、pwm、usart等功能&#xff0c;主要內容來源于B站“HPM_FAE”的視頻和官…

golang 依賴管理

目錄 演進過程 1. GOPATH 階段&#xff08;Go 1.0 - 1.10&#xff0c;2012 - 2018&#xff09; 2. Vendor 機制階段&#xff08;Go 1.5 實驗性引入&#xff0c;1.6 正式支持&#xff0c;2015 - 2018&#xff09; 3. Go Modules 過渡期&#xff08;Go 1.11 - 1.16&#xff0…

概率論—隨機事件與概率

文章目錄考綱術語事件的關系與運算關系運算古典概型概念和性質放入問題——隨機分配取出問題——簡單隨機抽樣問題幾何概型概率的性質與計算性質計算事件的獨立性和獨立的判定事件的獨立性判定定理舉反例的思想獨立試驗序列概型與n重伯努利概型錯題考綱 術語 (隨機)試驗隨機事…