【碎片八股文 #005】Hermes 引擎和 JSC 有什么区别?

112 阅读4分钟

【碎片八股文 #005】Hermes 引擎和 JSC 有什么区别?

一、面试题原文

面试官:  React Native 为什么要推出 Hermes 引擎?它和 JavaScriptCore 有什么区别?

候选人:  Hermes 好像更快吧……具体为什么不太清楚。

面试官心里想:  能说出字节码预编译、AOT、启动速度、内存优化就算过关了。


二、常见误答

很多人只知道"Hermes 性能更好",但说不清楚:

  • Hermes 的"快"体现在哪里?
  • 为什么 JSC 不够快?
  • Hermes 是如何做到更快的?

这些都是面试官会追问的点。


三、正确理解

什么是 JavaScript 引擎?

JavaScript 引擎负责 解析和执行 JS 代码

React Native 支持两种主流引擎:

引擎来源使用场景
JavaScriptCore (JSC)WebKit 项目(Apple)RN 默认引擎(iOS 系统内置)
HermesFacebook 开源RN 官方推荐(Android 优化显著)

Hermes 的核心设计目标

Hermes 是 Facebook 专门为 React Native 优化的引擎,三大目标

  1. 减少启动时间(App 打开更快)
  2. 降低内存占用(适合低端设备)
  3. 减少包体积(APK/IPA 更小)

四、核心区别对比

1. 编译方式:JIT vs AOT

特性JSCHermes
编译时机运行时 JIT 编译构建时 AOT 编译
代码格式JS 源码字节码(.hbc)
启动阶段边解析边执行直接执行字节码

JSC 的 JIT(Just-In-Time)编译:

应用启动 → 加载 JS 源码 → 解析成字节码 → JIT 编译 → 执行
              ↑                    ↑
           耗时操作              耗时操作

Hermes 的 AOT(Ahead-Of-Time)编译:

构建阶段:JS 源码 → 预编译成字节码 → 打包到 APK

应用启动:加载字节码 → 直接执行
                    ↑
                 超快!

关键差异:  Hermes 把编译工作提前到构建阶段,启动时直接执行字节码。

2. 启动性能对比

测试场景:  启动一个中型 RN 应用(包含 React Navigation)

指标JSCHermes提升
首屏时间~4.2s~2.1s50%
JS 解析耗时~1.8s~0.3s83%
内存占用~60MB~35MB42%

数据来源:  Facebook 官方测试(Android 中端机型)

3. 内存管理策略

JSC:

  • 采用 分代垃圾回收(Generational GC)
  • 内存占用较高,适合性能强的设备

Hermes:

  • 采用 增量式垃圾回收(Incremental GC)
  • 分片回收,减少卡顿
  • 专门优化低端设备的内存使用

4. 包体积影响

JSC:

  • JS bundle 是源码,体积较大
  • Android 需要额外集成 JSC 库(~1.5MB)

Hermes:

  • 字节码比源码更紧凑
  • 引擎本身体积更小(~1.2MB)
  • 整体包体积减少 15-20%

五、图解核心原理

JSC 的运行流程

┌─────────────────────────────────────────────────┐
│              应用启动阶段(运行时)                 │
│                                                 │
│  加载 index.js                                   │
│      ↓                                          │
│  词法分析 + 语法分析(慢)                          │
│      ↓                                          │
│  生成字节码                                       │
│      ↓                                          │
│  JIT 编译热点代码(慢)                            │
│      ↓                                          │
│  执行 JavaScript                                 │
└─────────────────────────────────────────────────┘

Hermes 的运行流程

┌─────────────────────────────────────────────────┐
│              构建阶段(打包时)                    │
│                                                 │
│  index.js → Hermes 编译器 → index.hbc(字节码)    │
└─────────────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────┐
│              应用启动阶段(运行时)                 │
│                                                 │
│  加载 index.hbc 字节码                            │
│      ↓                                          │
│  直接执行(超快!)                                │
└─────────────────────────────────────────────────┘

关键点:  编译工作前移,启动时零编译开销。


六、延伸提问

1. Hermes 有什么缺点?

缺点 1:不支持 eval() 和 new Function()

因为字节码是预编译的,运行时无法动态生成代码:

// 这段代码在 Hermes 中会报错
eval('console.log("hello")');  //不支持
new Function('x', 'return x * 2')(5);  //不支持

解决方案:  避免使用动态代码生成,改用静态方法。

缺点 2:调试体验稍差

由于是字节码,调试时看不到原始 JS 代码(需要 sourcemap 映射)。

缺点 3:iOS 上提升不明显

iOS 系统自带 JSC 且高度优化,Hermes 的优势主要在 Android。

2. 如何在 RN 项目中启用 Hermes?

Android(默认启用):

// android/app/build.gradle
project.ext.react = [
    enableHermes: true  // 默认就是 true
]

iOS(需要手动启用):

# ios/Podfile
use_react_native!(
  :hermes_enabled => true  # 启用 Hermes
)

然后重新安装 Pods:

cd ios && pod install

3. Hermes 和 V8 引擎有什么区别?

特性HermesV8
设计目标移动端优化高性能通用引擎
启动速度快(AOT)慢(JIT)
运行时性能中等极快(优化编译)
内存占用
包体积

结论:  Hermes 牺牲了部分运行时性能,换取启动速度和内存优势。

4. 为什么 Hermes 不做 JIT 优化?

JIT 的问题:

  • 需要在运行时编译,消耗 CPU 和内存
  • 移动设备性能有限,JIT 编译反而拖慢启动
  • iOS 系统不允许动态生成可执行代码(安全限制)

Hermes 的选择:  用 AOT 预编译 + 轻量级解释器,更适合移动端。


七、记忆口诀

"JSC 运行时编译慢,Hermes 提前编字节;启动快、内存省,移动端的好伙伴。"


八、碎片笔记

核心关键词:  Hermes、JSC、AOT、JIT、字节码、启动速度、内存优化

重点记忆:

  • JSC 是运行时 JIT 编译,Hermes 是构建时 AOT 编译
  • Hermes 启动速度提升 50%,内存降低 40%
  • Hermes 不支持 eval() 和 new Function()
  • iOS 上 Hermes 提升不如 Android 明显

实际应用:

  • 新 RN 项目优先使用 Hermes(Android 默认启用)
  • 避免在代码中使用动态代码生成(eval、new Function)
  • 如果有复杂计算逻辑,Hermes 的解释执行可能不如 JSC 的 JIT 快
  • 调试时记得配置 sourcemap,否则看不到原始代码

今天的碎片,帮你面试少挂一次。


下一篇预告:  【碎片八股文 #006】RunLoop 为什么能让主线程不退出?