React Native JSI 深潜(第一部分):React Native 架构——线程、Hermes 与事件循环
- 原文:React Native JSI Deep Dive — Part 1: React Native Architecture — Threads, Hermes, and the Event Loop
- 作者:Rahul Garg
- 原文发布:2026 年 3 月 16 日
“作为一个创造者,最危险的想法就是:你以为自己知道自己在做什么。” — Bret Victor,The Future of Programming,2013
摘要: 你已经做了多年 React Native。你知道 useState,知道 FlatList,知道怎么调 native module。但你知道从 onPress 触发到像素变化这 16 毫秒里发生了什么吗?三条执行线程、两个运行时环境(Hermes 上的 JavaScript 与 Objective-C/Java/C++ 的原生世界),再加上一套消息传递架构——它能解释你遇到过的每一个性能问题。
系列:React Native JSI 深潜(12 篇) 第一部分:React Native 架构——线程、Hermes 与事件循环(你正在阅读) | 第二部分:React Native Bridge vs JSI——到底变了什么、为什么变 | 第三部分:给 JavaScript 开发者的 C++ 入门 | 第四部分:你的第一个 React Native JSI 函数 | 第五部分:HostObjects——把 C++ 类暴露给 JavaScript | 第六部分:内存所有权 | 第七部分:平台接线 | 第八部分:线程与异步 | 第九部分:音频流水线 | 第十部分:存储引擎 | 第十一部分:模块方案对比 | 第十二部分:生产调试
崩溃现场(The Crash)
在开始之前,先看这个。这是一份来自生产环境的崩溃报告:一个基于 React Native + JSI 的 Android 音视频通话 App。它稳定运行了几个月,后来用户在通话中切后台时开始崩溃。
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 14329, tid: 14412 (AudioEncoder), name: com.callingapp
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7b1a4fe000
backtrace:
#00 pc 0x00004a8c4 /apex/.../bionic/libc.so (memcpy+180)
#01 pc 0x000018f30 /data/.../lib/arm64/libaudio.so
(audio::TxRingBuffer::push(uint8_t const*, unsigned long, long)+160)
#02 pc 0x00001c244 /data/.../lib/arm64/libaudio.so
(audio::CaptureEncoderThread::processFrame(audio::CaptureQueue&)+308)
#03 pc 0x00001bc80 /data/.../lib/arm64/libaudio.so
(audio::CaptureEncoderThread::run()+192)
#04 pc 0x0000b7e48 /apex/.../bionic/libc.so (__pthread_start(void*)+208)
--- thread 14329: main ---
#00 pc 0x000aee14 /apex/.../bionic/libc.so (__epoll_pwait+8)
#01 pc 0x00017630 /system/lib64/libutils.so (android::Looper::pollOnce(int)+188)
--- thread 14358: mqt_js ---
#00 pc 0x003c7a90 /data/.../lib/arm64/libhermes.so
(facebook::hermes::vm::Hades::collectOG()+1280)
#01 pc 0x003a4c24 /data/.../lib/arm64/libhermes.so
(facebook::hermes::vm::GCBase::finalizeUnreachableObjects()+172)
#02 pc 0x000018240 /data/.../lib/arm64/libaudio.so
(std::__ndk1::shared_ptr<audio::AudioPipelineHostObject>::~shared_ptr()+48)
#03 pc 0x000017b60 /data/.../lib/arm64/libaudio.so
(audio::AudioPipelineHostObject::~AudioPipelineHostObject()+64)
#04 pc 0x00001a380 /data/.../lib/arm64/libaudio.so
(audio::TxRingBuffer::~TxRingBuffer()+32)
#05 pc 0x003c28f4 /data/.../lib/arm64/libhermes.so
(facebook::jsi::Runtime::PointerValue::invalidate()+28)
你现在大概率还看不懂其中多数内容。这正是重点。
到这个系列结束时,你会读懂每一行:哪条线程崩了、当时在做什么、是谁释放了它正在读取的那段内存、为什么 GC 与 C++ 析构函数会在 buffer 所有权上发生冲突,以及该怎么修。每一篇都会解码更多栈帧。到了第 12 篇,你会像机械师读发动机报码一样读这份 trace。
先把这份日志记住。后面我们会回到它。
隐藏架构:三条线程、两个运行时
有件事应该让你警惕:你写 React Native 组件时,会感觉自己在写一个单体程序。
App.tsx
function Counter() {
const [count, setCount] = useState(0);
return (
<TouchableOpacity onPress={() => setCount(count + 1)}>
<Text>{count}</Text>
</TouchableOpacity>
);
}
一个文件。一个函数。一个心智模型:用户点击,状态变化,界面更新。看起来和 Web App 没区别。
但这是幻觉。你按下按钮时,事件会跨过三条执行线程,穿过两个运行时环境——JavaScript(Hermes)与原生(Objective-C/Java/C++)——并在互不共享内存、互不同步时钟、几乎“语言不通”的世界之间触发一连串消息。
你在 React Native 里遇到的每个性能问题——滚动卡顿、触摸响应延迟、native module 调用慢、神秘掉帧——都能追溯到这套三线程架构。理解它,不只是在解释问题,更是在让解法变得必然。
三条线程(The Three Threads)
React Native 不是“一个单线程 JavaScript 应用 + 若干原生视图”。它更像跑在手机上的一个分布式系统:多个独立执行域通过消息传递协作。官方架构文档强调两个主线程(JS 与 UI);Fabric 还会把布局放到后台线程。实践中 native module 的工作也会落在后台线程池,因此从工程心智模型看“三执行域”最有用。
线程 1:JavaScript 线程
你的代码在这里跑。组件、hooks、业务逻辑、API 调用,全都在 JS 引擎里执行。
在现代 React Native(0.70 起默认)里,这个引擎是 Hermes。与 Chrome 里的 V8 不同——V8 会先解释字节码(Ignition),再通过 Sparkplug 做 baseline 编译,并用 Maglev / TurboFan 对热点路径做优化——Hermes 走的是另一条路。Hermes 编译器 hermesc 会在构建阶段(Metro 打包后)把 JavaScript 预编译成字节码。应用发布时携带的就是这份字节码。启动时 Hermes 直接执行,不再做解析、JIT 编译、预热。
术语说明: Hermes 是针对 React Native 优化的 JavaScript 引擎。它采用预先字节码编译(
hermesc在打包后执行)、并发分代 GC(Hades),以及适配移动端约束的内存模型。它不是 V8(Chrome)也不是 JavaScriptCore(Safari),而是专门为 RN 设计。
JS 线程和浏览器一样,只有一个事件循环。onPress 触发时,回调进入队列;fetch 返回时,回调也进队列;timer 到点,还是同一个队列。一个线程按顺序逐个处理。
这意味着 JavaScript 在本质上是单线程的。当前 handler 在跑时,这条线程上别的事都不会发生。不会触发别的回调,不会处理状态更新。如果你的 handler 跑了 100ms(比如排序大数组),UI 就会卡 100ms——不是屏幕不能更新,而是 JS 线程没空处理下一项。
线程 2:UI(主线程)
这是平台主线程,负责真正绘制像素、处理触摸。iOS 上是 UIKit 所在线程,Android 上是 View 系统主线程。
UI 线程的硬约束是:必须在帧预算内向渲染流水线提交工作——16.6ms(60fps)或 8.3ms(120fps)。错过一帧,用户就会看到顿挫;错过多帧,界面就“坏了”。
这条线程不跑 JavaScript。它跑的是原生平台代码:视图层级更新、动画插值、触摸命中测试。(在新架构下,Yoga 布局会在 Fabric 的 commit 阶段于后台线程执行,而不是 UI 线程。)你的 React 组件并不存在于这里。这里存在的是原生视图:iOS 的 UIView、Android 的 android.view.View,由 React Native 渲染系统代你创建和管理。
关键洞察: 你在 React Native 写
<Text>Hello</Text>时,UI 线程并没有一个“Text 组件”。React Native 会创建一个原生视图:iOS 上是定制UIView子类(Fabric 中是RCTParagraphComponentView,底层 TextKit 渲染);Android 上是定制View子类。这些都是真正可被系统合成的原生视图。你的 React 树是“描述”,UI 线程持有的是“现实”。
线程 3:Native Modules(后台)
第三条线程,更准确说是一个后台线程池,负责 native module 工作。比如 JS 调 AsyncStorage.getItem('key'),真实读盘不会发生在 JS 或 UI 线程,而是分发到后台线程,完成后再把结果发回 JS。
相机访问、文件 I/O、生物认证、蓝牙——凡是和平台 API 打交道的 native module 基本都在这里。
它们如何通信:靠消息,不靠共享内存
关键点在这里:旧架构下,这三条线程不共享内存,只能通过序列化消息通信。新架构对此做了部分改变:Fabric 用了可被多线程读取的共享不可变 C++ 结构(如 shadow tree),JSI 允许 JS 线程直接调用 C++。但核心原则没变:线程通过定义良好的接口协作,而不是随意读写彼此的可变状态;多数跨线程交互仍是消息传递范式。
可以想象三个人在三个房间。旧架构下房间隔音,只能从门缝塞纸条。新架构下,部分房间多了只读公告板(immutable shadow tree)和直连对讲(JSI),但你仍然不能冲进别人的房间改桌面。
┌─────────────┐ messages ┌─────────────┐ messages ┌─────────────┐
│ │ ──────────▶ │ │ ──────────▶ │ │
│ JS Thread │ │ UI Thread │ │ Native │
│ (Hermes) │ ◀────────── │ (UIKit / │ ◀────────── │ Modules │
│ │ messages │ Android) │ messages │ │
└─────────────┘ └─────────────┘ └─────────────┘
当你的 onPress 里调用 setCount(count + 1),真实序列是:
- JS 线程:React 执行 handler,计算新的虚拟 DOM,和旧树 diff,发现
Text的内容从"0"变成"1"。 - JS 线程 → UI 线程:React Native 发出更新消息:“把原生视图 #42 的 text 属性更新为
'1'。” - UI 线程:收到消息,找到 #42,更新文本属性,并把它纳入下一帧渲染。
用户看到屏幕上的 “1”。这种简单更新通常能在一帧内完成(60fps 下约 16ms)。但如果任一线程忙碌——JS 正在做重计算,或 UI 正在合成复杂动画——更新就可能延迟到多帧之后。
Feynman Moment: “隔音房间”这个类比很有用,但有个关键例外:现实里门缝是固定的;React Native 的“传纸条机制”在新旧架构之间被彻底重写。旧架构是 JSON 序列化桥(慢、异步);新架构(含 Bridgeless)是 JSI——一套 C++ 接口,允许直接共享/调用。后面的系列核心都围绕这点展开。
跟踪一次按钮点击
从手指到像素,完整路径如下:
User Tap
│
▼
UI Thread (touch hit-testing)
│
▼
EventDispatcher → JSI
│
▼
JS Thread (onPress handler)
│
▼
React reconciliation (diff)
│
▼
Fabric commit
│
▼
UI Thread (native view update)
│
▼
Next frame render → pixel changes
逐步来看(下面的时间仅示意,真实耗时受设备、负载、复杂度影响;官方没有这条路径的亚毫秒级公开基准):
触摸开始。 用户手指触屏。操作系统在 UI 线程检测到触摸并做 hit-testing:指尖下面是哪一个原生视图?
触摸分发。 UI 线程识别出原生 TouchableOpacity,通过 React Native 的 EventDispatcher 把事件送到 JS 运行时:“在视图 #37 的 (x, y) 发生触摸开始。”(新架构里这一步经由 JSI,而不是旧桥。)
JS 处理事件。 JS 线程从事件循环队列取出事件。React 事件系统把 #37 映射到你的 onPress。然后执行:setCount(count + 1)。
React 协调(reconciliation)。 React 用新状态重跑组件,diff 新旧虚拟 DOM,发现一个变化:Text 子节点从 "0" 到 "1"。
Fabric 提交(commit)。 React Native 的现代渲染系统 Fabric 打包这次变化并提交。Fabric 取代了旧 UIManager,负责 JS 与 UI 之间的布局与挂载协同。提交内容大致是:“把 shadow node #42 的 text 属性设为 '1'。”
UI 线程应用更新。 UI 线程收到更新,修改原生视图,并标记视图层级需要重绘。
下一帧。 操作系统把更新后的视图层级合成进下一帧,用户看到 “1”。
对于简单计数器,这条链路通常能在一帧内完成(60fps 约 16ms)。JS 本身计算非常快——单文本变更的协调在现代设备上通常 <1ms。其余时间是“协调成本”:等事件到 JS、等 commit 到 UI、等下一帧边界。
现在想象一下:JS 工作从 4ms 变 40ms;或 UI 正在跑复杂动画;或 native module 调用又插入一次往返。消息开始堆积,线程失步,用户感知到卡顿。
事件循环(你的朋友,也是瓶颈)
JS 线程只有一个事件循环。触摸 handler、timer、网络回调、native module 返回值——全进同一个队列,按顺序一个个处理。
Event Loop Queue:
┌──────────────────────────────────────────────────┐
│ onPress() │ setTimeout() │ fetch() callback │ ... │
└──────────────────────────────────────────────────┘
▲ │
│ Process one at a time │
└─────────────────────────────────────────┘
这和浏览器 JavaScript 任务处理模型非常相似,也有同样结论:任何一个任务太慢,都会阻塞后面的所有任务。
阻塞事件循环的示例:
function onPress() {
// This blocks the entire JS thread for ~200ms
const sorted = hugeArray.sort((a, b) => a.localeCompare(b));
setData(sorted);
}
sort() 运行期间,不会处理新的触摸事件,不会驱动 JS 侧动画,不会触发网络回调,不会执行 timer。App 看起来像“冻住了”——不是 UI 线程卡死(它还能平滑绘制旧状态),而是 JS 线程无法告诉 UI 去改变任何东西。
Gotcha: 这也是为什么 React Native 动画应尽量避开 JS 线程。核心
AnimatedAPI 里需要useNativeDriver: true才能把动画完整下放 UI 线程。Reanimated(生产中主流库)通过 JSI 在 UI 线程跑 worklet,不需要useNativeDriver。原则相同:若动画由 JS 驱动,就需要每 16ms 从 JS 向 UI 发位置更新;JS 一忙就掉帧。UI 线程动画不依赖 JS 实时参与。
Hermes 怎么工作:字节码、Hades GC 与 jsi::Runtime
JS 线程不是“直接运行 JavaScript”,它运行的是 Hermes。Hermes 对 RN 至关重要的几个点如下:
字节码编译(Bytecode Compilation)
Chrome 里的 V8 会在运行时解析源码、生成字节码(Ignition)、再对热点路径走多层 JIT(Sparkplug / Maglev / TurboFan)。Hermes 在运行时跳过这一套:hermesc 在构建阶段(Metro 打包后、应用打包前)把 JS 编译成字节码。应用里携带的是 Hermes 字节码,而非源码。
V8 (Chrome): Source code → Parse → AST → Bytecode → Interpret (Ignition)
[hot paths only] → JIT compile (Sparkplug/Maglev/TurboFan) → Machine code
(parsing + interpretation at runtime; JIT warms up over time)
Hermes (RN): Source code → hermesc → Bytecode (at build time)
Bytecode → Execute directly (at runtime — fast startup)
这就是 Hermes 启动更快的原因:启动时不做解析。代价是 Hermes 没有 JIT,直接执行字节码,峰值算力通常不如 V8 这种 JIT 引擎。对 RN 常见负载(UI 驱动、事件驱动、非重计算)足够快;重计算本就该下沉到 native——这也正是本系列要教的方向。
Hades 垃圾回收器
Hermes 使用名为 Hades 的“多数并发”分代 GC。所谓“多数并发”,是指大部分回收工作在后台线程进行、与 JS 并行;但某些阶段(如 root 标记、弱引用 finalize)仍需短暂 stop-the-world。所谓“分代”,是把对象分为新生代与老生代:新生代对象(刚分配、寿命短)高频低成本回收,老生代(经历多次回收仍存活)低频回收。(历史上 32 位平台会用增量模式而非并发模式;但现在 32 位目标已越来越少。)
这很关键,因为 GC 暂停是 JavaScript 应用掉帧的常见元凶。Hermes 前代 GC(GenGC)在大堆场景下可能出现显著 STW 停顿。Hades 通过并发化把多数工作移出 STW,目标是把停顿量级相对 GenGC 降低约一个数量级;实际仍与堆大小和设备相关。
关键洞察: Hades 是 React Native 0.76+ “体感更丝滑”的原因之一。JavaScriptCore(旧默认引擎)也有并发 GC(Riptide,2017 引入),但 Hermes 的 GC 与其内存模型是按移动端约束专门调优的:更关注内存占用与启动时延,而非峰值吞吐。AOT 字节码 + Hades 的组合,使 Hermes 在移动端优势明显。
jsi::Runtime 接口
这就是后续系列最关键的入口。Hermes 不只是执行 JS,它还暴露了一套 C++ 接口:jsi::Runtime,让 native 代码能直接与 JavaScript 世界交互。
通过 jsi::Runtime,C++ 可以:
- 创建 JavaScript 对象与函数
- 调用 JavaScript 函数
- 读写 JavaScript 值
- 暴露可被 JavaScript 同步调用的 C++ 函数
这个接口就是 JSI(JavaScript Interface)。新架构建立在它之上。它替代了 JSON Bridge。后续系列会手把手教你用它。
但先别跑太快。此刻你只需要记住:Hermes 不是黑盒。它有 C++ API。native 可以“伸手”到 JS 世界,JS 也能“伸手”到 native 世界,而且不必把数据序列化成 JSON。
为什么这个模型重要
理解三线程架构不是学院派练习,它会直接决定你在本系列里每一个 native module 的行为。
线程归属(thread affinity):JSI 调用必须在 JS 线程。因为 jsi::Runtime 访问被限制在 JS 运行时线程;从其他线程访问 jsi::Runtime 或任意 jsi::Value 都是未定义行为——这些值绑定在特定 runtime 实例上,而 runtime 本身并非线程安全。这个单一约束几乎塑造了所有 native module 设计:重活放后台,结果通过 CallInvoker 或 RuntimeExecutor 回 JS。
消息传递(message-passing):旧桥把每次调用序列化成 JSON 再传消息。新架构(JSI)允许同步调用——但只限 JS 线程。何时用同步(快速查询,<1ms)、何时用异步(I/O、重计算、>5ms),是 native module 设计的核心能力。
事件循环(event loop):一个耗时 50ms 的同步 native call,会把 JS 线程整体阻塞 50ms。没有触摸、没有 timer、没有回调。这也是为什么音频流水线这类实时系统不能由 JS 驱动——事件循环保证顺序,但不保证墙钟时间确定性;队列拥塞、GC 停顿、设备负载都会让回调抖动。
本系列每一篇都可视为该架构的推论:
| Part | 架构推论 |
|---|---|
| Part 2 | Bridge 把消息序列化为 JSON;JSI 消除了这层序列化。 |
| Part 4 | JSI 函数在 JS 线程同步执行——快,但会阻塞。 |
| Part 5 | HostObjects 让 C++ 对象驻留 native 堆,JS 侧仅持句柄。 |
| Part 6 | JS GC 与 C++ 堆彼此独立——所有权必须显式定义。 |
| Part 8 | 后台线程必须用 CallInvoker 把结果送回 JS。 |
| Part 9 | 音频回调不能直接碰 JSI——它运行在另一条线程。 |
关键结论(Key Takeaways)
- React Native 的行为更像分布式系统。 三个执行域(JS、UI、Native 后台)通过定义良好的接口协作。新架构允许共享不可变数据与直连 JSI 调用,但线程依然不能自由访问彼此可变状态。多数性能问题都能追溯到这个架构。
- Hermes 是 JS 引擎。 它使用
hermesc的预编译字节码(启动快、无 JIT)、多数并发分代 GC(Hades,短 STW),并暴露 C++ 接口jsi::Runtime供 native 直接调用。 - JS 线程只有一个事件循环。 一个队列,一次一项。任何慢任务都阻塞后续所有事情:触摸、timer、网络回调、JS 驱动动画。
- UI 线程必须每 16ms 产出一帧。 它不跑 JavaScript,只跑原生平台代码。React 组件是“描述”,UI 线程才是“现实”。
jsi::Runtime只能在 JS 线程访问。 在其他线程访问 runtime/JSI 值是未定义行为。后台工作必须通过CallInvoker或RuntimeExecutor回 JS。若只记住一件事,就记这条。
再看那次崩溃(Reading the Crash)
再看开头那份 trace。你现在已经能解码第一层:线程名。
tid: 14412 (AudioEncoder):崩溃线程。它是 native 后台线程,不是 JS,也不是 main。说明崩在 native 层,与 JavaScript 不是同一执行域。thread 14329: main:Android 主/UI 线程。它当时空闲(__epoll_pwait,阻塞等待事件),UI 并未参与崩溃。thread 14358: mqt_js:JS 线程(RN 内部命名里是 message queue thread, JavaScript)。这里在跑Hades::collectOG,即 GC。也就是说 GC 与编码线程崩溃是并行发生的。
三条线程:一条崩、一条闲、一条在 GC。本篇的线程模型已经能告诉你:问题发生在 native 层,不在 JavaScript,也不在 UI。接下来 11 篇会解释 TxRingBuffer::push、AudioPipelineHostObject::~shared_ptr、Hades::collectOG 分别意味着什么,以及为什么它们不能安全地“同时发生”。
当前系统图(后续每篇加一层):
JS Thread UI Thread Native Thread
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Hermes │ │ Platform │ │ │
│ │ │ │ │ │
│ your code │ │ renders UI │ │ native work │
│ │ │ │ │ │
└───────────────┘ └───────────────┘ └───────────────┘
常见问题(FAQ)
React Native 应用里到底有几条线程?
三个主要执行域:JS 线程(Hermes + 你的 JS)、UI/主线程(原生视图渲染)、后台线程池(native module 的文件 I/O、网络等工作)。
React Native 里的 Hermes 是什么?
Hermes 是专为 React Native 设计的 JavaScript 引擎,使用预编译字节码与并发 GC(Hades),并针对移动端约束做优化。
React Native 的事件循环是什么?
JS 线程运行单事件循环,按顺序逐个处理触摸回调、timer、网络回调、native module 返回,模型与浏览器一致。
下一篇(What’s Next)
现在你已经有了架构底图:三条线程、消息传递、一个带 C++ API 的 JS 引擎。但我们还跳过了一个关键章节:在 JSI 之前,消息是怎么从 JS 到 native 的?
Legacy Architecture: New Architecture (Bridgeless):
JS Thread JS Thread
│ │
▼ ▼
Bridge Queue jsi::Runtime
(batched JSON messages) │
│ ▼
▼ Direct C++ call
Native deserializes │
│ ▼
▼ Native code
Native code
答案是 Bridge——一个 JSON 序列化层:简单、可靠、也非常慢。第二部分(即将发布)会完整追踪一次 native module 调用如何穿过 Bridge,为什么它必然成为瓶颈,以及 JSI 如何彻底去掉这层开销。
系列状态: 这是 12 篇系列的第 1 篇,系列仍在连载中。关注 heartIT 获取更新。
参考资料(References & Further Reading)
- React Native — The New Architecture(官方文档)
- Hermes — JavaScript Engine for React Native
- React Native 0.76 — New Architecture by Default
- React Native — Threading Model(架构文档)
- Meta Engineering Blog — Hermes: An Open Source JavaScript Engine Optimized for Mobile
- Hermes — Hades: Concurrent Garbage Collector
- WebKit — Introducing Riptide: Concurrent Garbage Collector
- React Native — Render, Commit, and Mount(渲染流水线文档)
RCTParagraphComponentView.h— Fabric Text Component(源码)
快速参考(Quick Reference)
React Native 线程模型
| Thread | Name in Traces | Runs | Constraint |
|---|---|---|---|
| JS Thread | mqt_js | Hermes、你的 JS/TS 代码、JSI 调用 | 单线程。所有 jsi::Runtime 访问必须在此线程。 |
| UI/Main Thread | main | 原生 UI 渲染、布局、触摸事件 | 平台主线程。绝不能阻塞。 |
| Native Background | (视实现而定) | 重型 native 工作、音频、网络、I/O | 不能访问 jsi::Runtime;必须用 CallInvoker 把结果回送 JS。 |
唯一铁律
jsi::Runtime 只能在 JS 线程访问。从任何其他线程访问都属于未定义行为。
系列:React Native JSI 深潜(12 篇) 第一部分:React Native 架构——线程、Hermes 与事件循环(你正在阅读) | 第二部分:React Native Bridge vs JSI——到底变了什么、为什么变 | 第三部分:给 JavaScript 开发者的 C++ 入门 | 第四部分:你的第一个 React Native JSI 函数 | 第五部分:HostObjects——把 C++ 类暴露给 JavaScript | 第六部分:内存所有权 | 第七部分:平台接线 | 第八部分:线程与异步 | 第九部分:音频流水线 | 第十部分:存储引擎 | 第十一部分:模块方案对比 | 第十二部分:生产调试