V8 源码视角下的 JavaScript 对象模型:Primitive、原型链与 ES6 类实现导读
V8 源码视角下的 JavaScript 对象模型Primitive、原型链与 ES6 类实现导读【免费下载链接】v8The official mirror of the V8 Git repository项目地址: https://gitcode.com/gh_mirrors/v81/v8本文面向 V8 开发者与对 JS 引擎实现感兴趣的同学基于 docs/javascript-overview.md 展开从 JavaScript 的两大值类别Primitive 与 Object出发结合 V8 内部的Map、JSObject、HeapNumber等核心数据结构讲清原型链的运行时表现、Foo.prototype与foo.__proto__的本质区别并剖析 ES6 class 语法糖在引擎层面的等价实现。读完本文你将具备以引擎视角阅读 V8 对象系统源码如 src/objects/js-objects.h、src/objects/map.h所需的完整知识背景。一、JavaScript 的两大值类别Primitive 与 ObjectJavaScript 中所有值都可以归入两大类原始值Primitives与对象Objects。这一划分不仅是语言层面的规范约定也直接决定了 V8 在堆上如何表示和存储这些值。1. 原始值Primitives原始值具有两个关键特征不可变Immutable原始值本身无法被修改任何修改操作都会产生一个新值按值传递Passed by value赋值或传参时复制的是值本身而不是引用。ECMAScript 规范中原始值包括undefined、null、booleantrue/false、number、string、bigint、symbol。注意一个常见的坑规范名称是大写形式如Undefined、Boolean而 JS 的typeof运算符返回的是小写字符串如undefined、boolean。从 V8 实现角度看原始值的存储方式并不统一这正是理解 V8 性能行为的关键SmiSmall Integer小整数直接存放在指针字pointer word内部无需堆分配访问开销极低。V8 利用指针对齐空出的低位比特来标记 Smi 类型这是一种典型的tagged pointer技巧其他原始值分配在堆上例如超出 Smi 范围的数字会退化为堆上的HeapNumber对象字符串则由String及其子类SeqString、ConsString、SlicedString等表示。src/objects/heap-number.h 中的注释明确写道The HeapNumber class describes heap allocated numbers that cannot be represented in a Smi (small integer)。也就是说当数字无法用 Smi 表示超出 31/32 位整数范围、是浮点数、NaN 等时V8 才把它提升为堆上分配的HeapNumber其内部以double value()的形式保存真正的数值。这也是为什么数字在 V8 里有时是 Smi、有时是 HeapNumber——同一段 JS 代码在不同阶段可能产生不同表示而这种表示差异会直接影响垃圾回收压力与属性访问性能。2. 对象Objects对象的特征恰好与原始值互补可变Mutable属性可以增删改按引用传递Passed by reference赋值或传参传递的是对象引用。JavaScript 的判定规则非常简洁任何不是原始值的值都是对象包括数组Array和函数Function。在 V8 内部对象统一由JSObject及其子类表示。从源码继承关系看src/objects/js-objects.hHeapObject是所有堆对象的基类JSReceiver继承自HeapObjectsrc/objects/js-objects.h它表示所有可以接收属性访问的对象JSObject继承自JSReceiversrc/objects/js-objects.h是普通 JS 对象的直接表示JSProxy同样继承自JSReceiversrc/objects/js-proxy.h内部持有target与handler两个槽位用于代理语义此外还有JSGlobalObject全局对象、JSObjectWithEmbedderSlots带嵌入器槽位的对象用于 C 嵌入场景等专门子类。需要特别警惕的一个陷阱是原始值包装对象Primitive wrapper objects例如new String(foo)、new Number(42)、new Boolean(true)。它们虽然名字里带着原始值类型但本质是真正的对象运行时为JSPrimitiveWrapper之类的堆对象拥有属性和方法与原始值foo、42、true完全不同。typeof new String(foo)返回object而非string这是区分它们最简单的手段。二、原型链V8 如何实现 JavaScript 的原型继承JavaScript 采用原型继承Prototypal Inheritance模型每个对象都有一个指向另一个对象的隐藏链接这个被链接的对象称为它的原型prototype语言规范中写作内部槽[[Prototype]]。1. 属性查找的完整链路当你访问一个对象的属性时引擎执行的查找顺序是先在对象自身上查找未找到则在其原型上查找再未找到继续在原型的原型上查找……由此形成一条原型链Prototype Chain链条终结于null通常情况下Object.prototype的[[Prototype]]就是null。这条链是 JS 继承与方法复用的底层机制Array.prototype上的push、map等方法正是通过原型链被每个数组实例共享到的。2.Foo.prototype与foo.__proto__的区别这是最容易混淆的一组概念务必区分清楚Foo.prototype是构造函数Foo自身的一个属性。它指向的那个对象将作为通过new Foo()创建的所有实例的原型foo.__proto__是实例foo上的访问器getter/setter。它暴露的是foo实际继承到的原型对象二者通过new关联起来如果foo new Foo()那么foo.__proto__ Foo.prototype恒成立。用一张图可以直观看出四者构造函数Foo、Foo.prototype、实例foo、Object.prototype的关系digraph G { rankdirTB; node [shaperecord]; edge []; Foo [labelConstructor: Foo]; Foo_Proto [labelFoo.prototype]; foo [labelInstance: foo]; Obj_Proto [labelObject.prototype]; null_node [labelnull]; Foo - Foo_Proto [label.prototype]; Foo - foo [labelnew Foo, styledashed]; foo - Foo_Proto [label[[Prototype]]]; Foo_Proto - Obj_Proto [label[[Prototype]]]; Obj_Proto - null_node [label[[Prototype]]]; }图中可以看到两个维度的链接Foo - Foo.prototype构造函数与其原型对象之间的.prototype显式属性与foo - Foo.prototype - Object.prototype - null实例沿[[Prototype]]一路向上的原型链。3.__proto__与 V8 的实现细节几点实现层面的重要事实__proto__本质是挂在Object.prototype上的访问器属性accessor property即 getter/setter它负责把内部的[[Prototype]]链接暴露给 JS 代码现代 JavaScript 规范更推荐使用Object.getPrototypeOf()与Object.setPrototypeOf()来读写原型而非直接操作__proto__在 V8 中原型指针存储在对象的Map里。也就是说Map不仅描述对象的形状属性布局还记录着对象的原型。因此修改一个对象的原型会改变它的Map最后一点至关重要。Map即隐藏类Hidden Class是 V8 对象系统的心脏。查看 src/objects/map.h 中关于Map对象布局的注释约第 180253 行可以看到Map的关键字段依次包括instance_size实例大小instance_type实例类型区分JSObject、String、HeapNumber等bit_field包含is_callable、is_constructor、is_undetectable等布尔标志bit_field3包含is_prototype_map该 Map 是否作为某个原型对象使用、is_dictionary_map是否退化为字典模式、is_deprecated、is_extensible等[prototype]指向该 Map 所代表对象的原型即[[Prototype]]的真实存储位置[constructor_or_back_pointer_or_native_context]记录构造函数或原生上下文[prototype_validity_cell]原型有效性单元用于优化当原型变化时会使依赖它的内联缓存失效如果该 Map 是原型 Map则持有[prototype_info]否则持有[raw_transitions]记录形状迁移。正因为原型存放在Map中V8 才能把对象形状 原型打包成稳定的编译期知识只要对象的Map不变TurboFan 等优化编译器就可以放心地对属性访问进行内联缓存与去虚拟化。而一旦通过__proto__或Object.setPrototypeOf()改了原型Map就会变化甚至触发从快属性模式到字典模式的降级相关优化随之失效——这是实际工程中应当尽量避免动态改原型的原因之一。从源码注释也能看到V8 在 src/objects/transitions.h 与 src/objects/map-updater.h 中专门处理了通过__proto__访问器设置原型时产生的 Map 迁移transition逻辑例如对象字面量{__proto__: foo, a: 1}与随后赋值产生的迁移图。三、ES6 Class原型继承的语法糖与其等价形式ES6 引入的class语法主要是对既有原型继承模型的语法糖syntactic sugar并没有引入新的继承语义——class背后依然是构造函数、prototype对象与[[Prototype]]链接的组合。考虑下面的类定义class Animal { constructor(name) { this.name name; } speak() { console.log(${this.name} makes a noise.); } } class Dog extends Animal { speak() { console.log(${this.name} barks.); } }在引擎层面这段代码大致等价于传统的构造函数写法function Animal(name) { this.name name; } Animal.prototype.speak function() { console.log(this.name makes a noise.); }; function Dog(name) { Animal.call(this, name); } Dog.prototype Object.create(Animal.prototype); Dog.prototype.constructor Dog; Dog.prototype.speak function() { console.log(this.name barks.); };逐行解读等价关系class Animal { constructor(name) {...} }对应function Animal(name) {...}——类的构造器就是普通构造函数类体内的speak() {...}方法定义等价于Animal.prototype.speak function() {...}——类方法全部挂载在prototype对象上这正是实例通过原型链共享方法的原因class Dog extends Animal对应两件事Animal.call(this, name)在子类构造函数中显式调用父类构造函数完成初始化Dog.prototype Object.create(Animal.prototype)把子类的prototype对象挂到父类prototype之下从而建立Dog.prototype - Animal.prototype - Object.prototype - null的原型链Dog.prototype.constructor Dog恢复constructor字段Object.create产生的对象默认不指向Dog。需要注意class语法与手写构造函数并不完全等价class声明的类不可直接以普通函数方式调用必须new类体内的方法默认不可枚举non-enumerable且类声明存在暂时性死区等差异。但从对象模型角度看二者共享完全相同的底层机制——prototype对象 [[Prototype]]链接。结合前文源码可以看到这套机制在 V8 中的落点Map的[constructor_or_back_pointer_or_native_context]字段记录了对象与其构造函数的关联src/objects/js-function.h 中关于__proto__的注释也印证了构造函数实例的原型即Foo.prototype将在new时被设置为实例的__proto__这一流程。四、深入 V8 对象系统相关源码文件导读理解上述概念后可以按图索骥深入阅读 V8 源码。下表列出与本文主题直接相关的核心文件及其作用文件路径核心内容src/objects/js-objects.hJS 对象体系定义JSReceiver、JSObject及其众多子类JSGlobalObject、JSObjectWithEmbedderSlots等src/objects/map.hMap隐藏类定义对象形状、[prototype]原型指针、bit_field3标志位is_prototype_map、is_dictionary_map等src/objects/js-proxy.hJSProxy定义target与handler槽位对应 JSProxy语义src/objects/heap-number.hHeapNumber定义堆上分配的、无法用 Smi 表示的浮点/大整数src/objects/transitions.hMap 迁移transition机制含通过__proto__设置原型时的处理注释src/objects/map-updater.hMap 更新器处理__proto__赋值等引发的 Map 迁移图阅读建议先读 src/objects/map.h 中Map对象的布局注释约第 180253 行建立对象 Map描述形状与原型 数据字段的心智模型再到 src/objects/js-objects.h 查看JSObject的类声明与属性访问器接口最后回到 JS 侧用Object.getPrototypeOf()、Object.setPrototypeOf()与Map的概念相互印证即可完成从语言语义到引擎实现的贯通。五、小结本文以 docs/javascript-overview.md 为骨架系统梳理了理解 V8 所需的 JavaScript 对象模型知识Primitive 与 Object 的两分法原始值不可变、按值传递其中 Smi 直接内嵌于指针字其余原始值如HeapNumber、String堆分配对象可变、按引用传递统一由JSObject/JSReceiver体系表示原型链机制属性查找沿[[Prototype]]逐级上溯至nullFoo.prototype是构造函数的属性foo.__proto__是实例暴露原型的访问器二者通过new建立foo.__proto__ Foo.prototype的关系V8 实现落点原型指针存放在对象的Map中改原型即改MapMap的bit_field3还携带is_prototype_map等关键标志class语法糖最终落到构造函数、prototype对象与Map的constructor字段之上。掌握这套模型是进一步阅读 V8 对象系统、属性访问优化内联缓存/隐藏类、TurboFan 类型推断乃至 GC 相关源码的必备前提。【免费下载链接】v8The official mirror of the V8 Git repository项目地址: https://gitcode.com/gh_mirrors/v81/v8创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

HUND:基于神经下降的视觉三维人体姿态与形状重建方法解析

HUND:基于神经下降的视觉三维人体姿态与形状重建方法解析

人工智能深度学习NLP计算机视觉强化学习 【免费下载链接】google-research Google Research 项目地址: https://gitcode.com/gh_mirrors/go/google-research 点击查看 免费下载 导读 HUND(HUman Neural Descent,人体神经下降)是…

2026/9/24 15:18:14 阅读更多 →
Ent Go 框架谓词(Predicates)完全指南:字段过滤、边查询、自定义 SQL 与 JSON 谓词实战

Ent Go 框架谓词(Predicates)完全指南:字段过滤、边查询、自定义 SQL 与 JSON 谓词实战

Ent Go 框架谓词(Predicates)完全指南:字段过滤、边查询、自定义 SQL 与 JSON 谓词实战 【免费下载链接】ent An entity framework for Go 项目地址: https://gitcode.com/gh_mirrors/en/ent Ent 是面向 Go 的实体框架(Ent…

2026/9/24 16:49:48 阅读更多 →
AI辅助前端编码效率提升指南:从提示词工程到AI Agent实践

AI辅助前端编码效率提升指南:从提示词工程到AI Agent实践

写前端代码这些年,我最早接触AI辅助编码的时候,心态特别朴素——“帮我写个登录页”“帮我写个轮播图”,复制粘贴,跑不通再问一把,能用就行。后来真正把AI用出效率,是在某个版本迭代压力很大的阶段&#xf…

2026/9/23 10:11:34 阅读更多 →

最新新闻

低功耗电压检测电路:MOS管开关控制电阻分压,将待机电流降至nA级

低功耗电压检测电路:MOS管开关控制电阻分压,将待机电流降至nA级

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:59:53 阅读更多 →
Arduino IDE安装ESP8266卡在99%?换源与离线包方案彻底解决

Arduino IDE安装ESP8266卡在99%?换源与离线包方案彻底解决

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:59:53 阅读更多 →
Flowbite 搜索输入框组件完全指南:从基础搜索栏到语音搜索与高级筛选

Flowbite 搜索输入框组件完全指南:从基础搜索栏到语音搜索与高级筛选

UI组件前端 【免费下载链接】flowbite Open-source UI component library and front-end development framework based on Tailwind CSS 项目地址: https://gitcode.com/gh_mirrors/fl/flowbite 点击查看 免费下载 搜索框是每个站点的“入口级”交互组件。本篇基于…

2026/9/25 4:59:53 阅读更多 →
ESP32-C3 当管家:软件模拟 SWD 实现 RP2040 固件下载与日志采集

ESP32-C3 当管家:软件模拟 SWD 实现 RP2040 固件下载与日志采集

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:59:53 阅读更多 →
产线烧录良率排查全攻略:从硬件连接到固件格式的链路诊断

产线烧录良率排查全攻略:从硬件连接到固件格式的链路诊断

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:59:53 阅读更多 →
从 Codex CLI 到知识库:TaoToken 统一 Key 驱动的 AI 代理个人知识管理全流程

从 Codex CLI 到知识库:TaoToken 统一 Key 驱动的 AI 代理个人知识管理全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:58:52 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →