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重伯努利概型错题考纲 术语 (随机)试验随机事…