想象一家跨国科技公司有两个分部:JS 分部只讲 JavaScript 语,负责界面逻辑与业务编排;原生分部只讲 Java/Objective-C 语,掌握摄像头、定位、文件系统这些“硬技能”。两个分部隔着大洋,既不能直接对话,也不能共享办公室,所有协作都只能通过公司内部唯一的“桥接邮局”——Bridge——传递标准化信件(JSON 消息)。这套邮局系统的效率,直接决定了整个 App 的响应速度。后来,管理层先后推出了“现代化快递分拣中心”(TurboModule)和“直拨专线电话”(JSI)两轮改革。本文就用这个“两地通信”的比喻,把 React Native 与原生端的通信事件机制讲个明白。

岗位关联

理解 RN 与原生端的通信机制,是跨平台开发和性能优化的核心技能,也是 AI 辅助移动端工程落地时绕不开的基础知识。

目录
  1. 可视化演示
  2. Bridge 桥接机制
  3. 消息传递流程
  4. 事件订阅机制
  5. 通信性能瓶颈
  6. TurboModule 改进
  7. JSI 新架构
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

演示RN与原生端的通信机制:JS Bridge序列化消息传递、事件订阅模式与JSI直连的延迟差异。

本节配有交互动效(进入视口后自动加载,约 3KB)。

JS Bridge序列化 JS调用 序列化 JSON Bridge 消息经桥接序列化传递

Bridge 桥接机制

想象一家跨国公司的两个分部:JS 分部只讲 JavaScript 语,负责界面逻辑和业务编排;原生分部只讲 Java/Objective-C 语,掌管摄像头、定位、文件系统等“硬技能”。两个分部位于大洋两岸,既不能直接对话,也不能共享办公室,一切协作都只能通过公司内部唯一的“桥接邮局”——Bridge——来完成。邮局的吞吐效率,就是两个分部协作效率的天花板。

在 React Native 的架构里,Bridge 就是这座邮局。JS 跑在一个独立的 JavaScriptCore(或 Hermes)运行时里,原生代码跑在各自平台的运行时里,两者完全隔离,互不感知。任何一次跨端请求,都必须先写成一封“标准信件”——一个 JSON 格式的消息对象——才能投递给对方。这套设计让两端彻底解耦、互不阻塞,但也意味着每一次协作都要走完整的“写信—寄信—收信”流程,产生肉眼不可见但真实存在的时延。

Bridge 自 2015 年随 RN 发布以来,一直维护着 JS 调用原生的消息队列、原生回调 JS 的队列以及事件消息队列。所有消息都被序列化为 JSON 字符串,通过桥接线程异步搬运。端到端总时延可以写成四个环节的叠加:

这座“邮局”奠定了 RN 的一切能力基础,却也埋下了性能隐患。后续所有架构演进,本质上都是在优化这四个环节中的某一个。

Ttotal=Tserialize+Tqueue+Ttransfer+Tdeserialize\displaystyle T_{\text{total}} = T_{\text{serialize}} + T_{\text{queue}} + T_{\text{transfer}} + T_{\text{deserialize}}
一次跨端协作 = 写信 + 排队 + 邮车运输 + 拆信。四个环节任何一个变慢,整个 App 都会跟着卡顿。
  1. T_serialize(序列化时延)JS 侧把调用参数编码成 JSON 字符串所耗费的时间。
    Tserialize\displaystyle T_{\text{serialize}}
  2. T_queue(排队时延)消息在桥接队列中等待批量发送的时间。
    Tqueue\displaystyle T_{\text{queue}}
  3. T_transfer(传输时延)桥接线程把消息搬运到原生侧的时间。
    Ttransfer\displaystyle T_{\text{transfer}}
  4. T_deserialize(反序列化时延)原生侧读取 JSON 字符串、恢复出原始参数结构的时间。
    Tdeserialize\displaystyle T_{\text{deserialize}}

消息传递流程

现在把镜头拉近,看一封“跨洋信件”从 JS 分部寄到原生分部的完整旅程。写信人(JS 代码)要先确认三件事:寄给哪个部门(模块 ID)、办什么业务(方法 ID)、附上什么材料(参数)。邮局工作人员把这些信息严格填入标准表单,投进待发邮箱。

在代码层面,当 JS 调用原生模块方法时,React Native 的 MessageQueue 并不会立即把请求发出去,而是先构造一条消息:模块 ID + 方法 ID + 参数 + 回调 ID,然后推入 JS 侧消息队列。队列不会逐条投递,而是攒到一定数量或到达特定时机后,才批量打包发送给原生线程。这个过程在通信事件机制里叫“队列批量发送”。

原生端收到一批消息后,逐条反序列化并执行相应的原生方法;执行完毕,把结果连同回调 ID 一起回传到 JS 侧队列,JS 侧根据回调 ID 找到最初的调用点,触发回调或 resolve Promise。消息在队列里堆积得越多,整体等待越久:

就像发往同一城市的信件会凑成一麻袋统一装车,批量确实节省了运输成本;但一旦赶上“双十一”,分拣中心信件堆积成山,每一封的等待时间反而暴涨。RN 开发者遇到的许多卡顿,根源往往不是原生执行得慢,而是“邮局门口堵车了”。

Tqueue=N×SB\displaystyle T_{\text{queue}} = \frac{N \times S}{B}
排队时延 = 队列里的消息条数 × 单条消息大小 ÷ 桥接带宽。消息越多、越大,排队越久。
  1. N(消息条数)当前堆在队列里的等待消息数量。
    N\displaystyle N
  2. S(单条消息大小)一条消息经 JSON 序列化后的字节数。
    S\displaystyle S
  3. B(桥接吞吐带宽)桥接线程单位时间能搬运的字节量。
    B\displaystyle B

事件订阅机制

上面的流程都是 JS 主动“寄信”给原生。但原生想主动通知 JS 怎么办?比如网络从 Wi-Fi 切到 5G,或后台下载完成——总不能等 JS 每隔一秒“查岗”。原生分部于是建了一座“广播电台”:它发射信号(事件名 + 数据),JS 分部只需要订阅收听即可。

这套广播收听的机制,在 RN 里由 NativeEventEmitter 和 DeviceEventEmitter 提供。原生模块像电台一样广播事件,JS 端通过 addListener 注册监听回调。当事件消息通过桥接到达 JS 侧时,RN 根据事件名查表,找到对应的监听器列表并逐个调用回调。这就是事件订阅模式的核心。

事件机制有两个坑。第一,事件消息同样要走 Bridge,仍然有序列化和排队开销;第二,JS 端必须手动维护监听生命周期。组件卸载时若忘记 removeListener,监听器会继续残留在事件表里,形成“幽灵订阅”——这是内存泄漏的经典来源:

这就像搬家后忘了向广播电台退订,电台依然每天往旧地址寄节目单。回调对象永远不会被回收,App 越用越臃肿,最终被系统“制裁”。记住通信事件机制的铁律:注册与释放,永远成对出现。

Ntotal=Nactive+Nleaked\displaystyle N_{\text{total}} = N_{\text{active}} + N_{\text{leaked}}
事件表里的监听器总数 = 正常存活的监听器 + 泄漏残留的监听器。注册和释放不成对,泄漏就会累积。
  1. N_total(监听器总数)事件表中某个事件名对应的全部监听器条目。
    Ntotal\displaystyle N_{\text{total}}
  2. N_active(活跃监听数)组件仍存活、需要正常接收事件回调的监听器。
    Nactive\displaystyle N_{\text{active}}
  3. N_leaked(泄漏监听数)组件卸载后未被移除、残留在事件表中的监听器。
    Nleaked\displaystyle N_{\text{leaked}}

通信性能瓶颈

为什么老一代 RN 在列表快速滚动或频繁交互时会明显卡顿?回到邮局场景:每一封信都要手工誊写(序列化),高峰期排长队(队列积压),邮路还只有一条(单线程桥接)。业务量小的时候游刃有余,流量一大,瓶颈立刻现形。

从工程角度,传统 Bridge 的延迟主要来自四个方面。一是序列化开销随消息体量线性增长;二是高频小消息让队列频繁批处理,排队时延被不断拉高;三是单条大消息会长时间独占桥接线程;四是 JS 线程与原生线程之间的切换本身也有成本。其中序列化开销是最大的元凶。

把 JS 对象转成 JSON 字符串、再把字符串解析回原生对象,这个过程的 CPU 消耗与负载大小成正比:

传一个 10 字节的小参数,开销只有几十微秒;但若把整张图片的 base64(几百 KB)塞进桥,一次序列化就能让 UI 线程明显掉帧。这正像邮局里每封信都要称重贴票、录入系统,碰到一个“巨大的包裹”,整个柜台都要被占用很久。于是设计师开始思考:与其让所有业务挤在一条邮路上,不如给关键部门装一部“直拨电话”。

C=α×L+β\displaystyle C = \alpha \times L + \beta
序列化开销 = 单位字节处理成本 × 负载大小 + 固定启动成本。数据越大,代价越吓人。
  1. α(单位开销系数)每字节负载消耗的 CPU 时间,取决于引擎与平台实现。
    α\displaystyle \alpha
  2. L(负载大小)传入数据的规模,比如字符串长度或对象包含的字段数。
    L\displaystyle L
  3. β(固定启动成本)创建 JSON 容器等固定开销,与负载大小无关。
    β\displaystyle \beta

TurboModule 改进

面对邮局拥堵,管理层推出的第一轮改革叫 TurboModule——相当于把旧邮局升级成现代化快递分拣中心:寄件人不再手写地址,而是用电子面单(接口定义)自动录入;不再每次都在柜台翻箱倒柜找收件人,而是事先建好客户档案(模块常驻引用)。

TurboModule 是 RN 新架构的四大支柱之一。旧版 Bridge 中,JS 每次调用原生模块,都要在模块表里动态查找、组装消息、装箱拆箱。TurboModule 则通过 Codegen 代码生成器,在构建期就根据接口定义生成类型安全的胶水代码,JS 端持有原生模块的常驻引用,调用时直接进入原生方法,省去了大量动态查找和类型转换操作。

结合前面的时延模型,TurboModule 的收益可以这样量化:

TurboModule 还支持懒加载:JS 端第一次真正用到某个模块时才初始化,App 启动时不再把几百个原生模块全部加载一遍。从旧邮局到现代化快递中心,TurboModule 确实让“寄件”快了不少,但它仍然没有根治问题——信件该序列化还是序列化,该排队还是排队。真正釜底抽薪的,是下一轮通信架构的革命。

η=Tlegacy−TturboTlegacy×100%\displaystyle \eta = \frac{T_{\text{legacy}} - T_{\text{turbo}}}{T_{\text{legacy}}} \times 100\%
η 表示调用时延的下降比例:老桥接耗时减去新模块耗时,再除以老桥接耗时。在社区的公开基准中,TurboModule 让原生模块调用时延普遍下降 40% 以上。
  1. T_legacy(传统调用时延)旧 Bridge 下一次完整调用的总耗时。
    Tlegacy\displaystyle T_{\text{legacy}}
  2. T_turbo(Turbo 调用时延)TurboModule 下同一次调用的总耗时。
    Tturbo\displaystyle T_{\text{turbo}}
  3. η(时延下降比例)优化带来的相对提升,η 越大,收益越明显。
    η\displaystyle \eta

JSI 新架构

如果说传统 Bridge 是邮局,那么 JSI(JavaScript Interface,JavaScript 接口层)就是两个分部之间拉起的“直拨专线”。不需要写信,不需要寄件,不需要邮车——两个部门的人拿起电话就能直接通话,甚至可以把同一份文件同时摊在两张桌子上看。

JSI 是 RN 新架构的底层基石,取代了旧的 Bridge 通信通道。它提供了一层轻量级 C++ 接口,让 JavaScript 引擎可以直接持有原生 C++ 对象的引用,并直接调用其方法。对象引用直接传递,不再需要 JSON 序列化和反序列化;调用既支持异步,也支持同步执行。

在 JSI 模式下,前文的时延模型被大幅改写:

从“跨洋寄信”到“专线通话”,JSI 让 JS 与原生不再是两个只能靠邮局沟通的孤岛,而更像是同一间办公室里随时可以转头讨论的同事。当然,专线电话需要铺设光缆——JSI 引入了大量 C++ 代码,性能提升背后是复杂度的上升。理解这层取舍,才是真正的架构智慧。

Tjs→nativeJSI=Tcall,Tserialize→0\displaystyle T_{\text{js} \rightarrow \text{native}}^{\text{JSI}} = T_{\text{call}} ,\quad T_{\text{serialize}} \rightarrow 0
在 JSI 下,序列化项归零,调用只剩真正的函数执行成本。Fabric 渲染器和 TurboModule 都构建在 JSI 之上,实现了内存级的数据复用。
  1. T_call(直调开销)JSI 下调用经接口直接进入 C++ 方法,仅剩函数调用本身的成本。
    Tcall\displaystyle T_{\text{call}}
  2. T_serialize → 0(序列化归零)对象引用直接传递,JSON 打包与解析开销趋近于零。
    Tserialize→0\displaystyle T_{\text{serialize}} \rightarrow 0

AI 应用场景

跨平台 App 通信瓶颈优化

阶段:性能诊断与优化

解决的问题:传统 Bridge 下,高频桥接调用导致队列积压、UI 掉帧。人工打点分析通信日志,既耗时又难定位根因。AI 可以自动分析调用日志,识别高频模块访问、超大负载消息和异常排队时延,并给出合并调用、迁移到 TurboModule/JSI 等优化建议。

具体案例:某电商 App 在商品列表页滚动时频繁调用原生模块读取缓存,AI 分析日志后发现每秒有数百条小消息过桥,建议将多次小调用合并为一次批量快照读取,修复后页面帧率从 42fps 提升到 58fps。

AI 辅助生成 TurboModule 胶水代码

阶段:AI 辅助代码生成

解决的问题:手写 TurboModule 需要同时维护 JS 接口、原生实现和 Codegen 配置三处代码,还要保证类型对齐,繁琐且易错。AI 可以根据原生模块的接口定义,自动生成类型安全的 JS 调用包装器和原生侧胶水代码,并补全异常处理。

具体案例:开发者提供一个 Objective-C 方法定义,AI 生成对应的 TurboModule 接口声明、JS 封装函数和 C++ 桥接代码,随后直接通过类型检查并跑通调用链路。

NativeEventEmitter 泄漏检测

阶段:可观测性与稳定性分析

解决的问题:事件监听器未成对释放导致的内存泄漏很难肉眼排查。AI 对 addListener / removeListener 的调用日志做序列匹配分析,自动标记未配对的监听器,并定位到泄漏页面与组件。

具体案例:在长列表页面反复进出 20 次后,AI 检测到“网络状态”事件监听器数量持续增长,定位到某个弹窗组件未在卸载时清理监听,修复后内存占用回落 12%。

Python 演示

python85 行
# -*- coding: utf-8 -*-# 简化模拟: RN Bridge 的消息队列批量发送与事件订阅/释放import jsonfrom collections import deque  class BridgeSimulator:    """模拟传统 Bridge: 消息批量入队/批量发送, 支持事件订阅与释放"""     def __init__(self, batch_size=5):        self.call_queue = deque()       # 待发送的调用消息队列        self.listeners_map = {}         # 事件名 -> 回调函数列表        self.batch_size = batch_size    # 每次批量发送的消息条数        self.next_callback_id = 0       # 回调 ID 自增器        self.total_calls = 0            # 累计调用计数     def call_native(self, module_id, method_id, args, callback):        """JS 端发起调用: 序列化后入队, callback 等待原生回传结果"""        msg = {            "module_id": module_id,            "method_id": method_id,            "args": args,            "callback_id": self.next_callback_id,        }        self.call_queue.append(json.dumps(msg))  # 模拟 JSON 序列化        self.next_callback_id += 1        self.total_calls += 1        callback("ok")     def flush(self):        """原生端批量取出消息并反序列化, 模拟批量发送"""        batch = []        while self.call_queue and len(batch) < self.batch_size:            raw = self.call_queue.popleft()            batch.append(json.loads(raw))  # 模拟反序列化        return batch     def emit_event(self, event_name, data):        """原生端广播事件: 给所有订阅者投递消息, 模拟 NativeEventEmitter"""        if event_name not in self.listeners_map:            return        payload = json.dumps({"event": event_name, "data": data})        for callback in list(self.listeners_map[event_name]):            callback(payload)     def add_listener(self, event_name, callback):        """JS 端添加监听, 等价 NativeEventEmitter.addListener"""        self.listeners_map.setdefault(event_name, []).append(callback)     def remove_listener(self, event_name, callback):        """JS 端移除监听, 等价 NativeEventEmitter.removeListener"""        if event_name in self.listeners_map:            self.listeners_map[event_name].remove(callback)  def on_network_change(raw):    msg = json.loads(raw)    print("收到事件: {} -> {}".format(msg["event"], msg["data"]))  def main():    bridge = BridgeSimulator(batch_size=3)    # JS 端订阅事件    bridge.add_listener("networkChange", on_network_change)     # JS 端发起两次原生调用    bridge.call_native("NetInfo", "getStatus", [], lambda r: print("回调:", r))    bridge.call_native("Camera", "open", {"mode": "photo"}, lambda r: print("回调:", r))     # 原生端批量处理队列    batch = bridge.flush()    print("原生批量处理 {} 条消息:".format(len(batch)))    for msg in batch:        print("  - module={} method={}".format(msg["module_id"], msg["method_id"]))     # 原生端主动广播事件    bridge.emit_event("networkChange", {"online": True, "type": "wifi"})     # JS 端取消订阅: 注册与释放必须成对    bridge.remove_listener("networkChange", on_network_change)    bridge.emit_event("networkChange", {"online": False})  # 不再有任何输出  if __name__ == "__main__":    main()
目的:用约 80 行 Python 模拟 RN 传统 Bridge 的三大核心行为——消息批量入队与发送、原生广播事件、JS 端订阅与释放,帮助读者把正文中的抽象模型跑起来。
  1. self.call_queue.append(json.dumps(msg))模拟 JS 端调用原生时,把消息序列化为 JSON 字符串后投入桥接队列。
  2. batch = bridge.flush()模拟原生端一次性从队列中批量取出多条消息进行处理。
  3. json.loads(raw)模拟原生端反序列化 JSON,恢复出模块 ID、方法 ID 和参数。
  4. bridge.add_listener("networkChange", on_network_change)等价于 RN 中 NativeEventEmitter 的 addListener,注册事件回调。
  5. self.listeners_map.setdefault(event_name, []).append(callback)按事件名维护监听器列表,事件到达时按列表分发。
  6. bridge.remove_listener("networkChange", on_network_change)模拟 removeListener 手动释放监听,防止泄漏。

常见误区

误区既然新架构 JSI 这么强,传统 Bridge 是不是马上就被完全废弃了?新项目完全不需要了解 Bridge 了。

正解JSI 确实是新架构的基石,但存量 RN 项目中大量旧原生模块仍通过兼容层(Interop Layer)以 Bridge 的异步消息模型工作。Bridge 的“队列批量 + JSON 序列化”思想也仍是理解 RN 通信事件机制的基础。理解两者能共存,才算真正读懂 RN 架构演进。

为何常见:技术文章喜欢强调 JSI 的“颠覆性”,让读者误以为新旧架构是瞬间切换,忽略了渐进兼容的现实。

误区JS 调用原生方法后,消息会像网络请求一样立即逐条到达原生端,实时性很强。

正解传统 Bridge 的消息是批量异步发送的。JS 端把消息放进队列,由 MessageQueue 攒够一批或在特定时机才统一发送。这也是为什么高频小调用会带来明显的排队时延。

为何常见:现代通信给我们的直觉是“发出即到达”,很少有人意识到 RN 桥接内部存在队列攒批的机制。

误区JS 调用原生方法后,JS 线程会傻等结果返回,期间 UI 卡住不动。

正解传统 Bridge 是异步的,JS 调用后立刻继续执行,结果通过回调或 Promise 在原生处理完后送回。只有 JSI 才提供同步调用能力,且那是新架构的主动选择,而不是桥接的默认行为。

为何常见:把 JSI 的同步能力错误地投射到传统 Bridge 上,或者把某些同步 API 的体验当成了底层机制。

误区addListener 注册的监听器,组件卸载后 RN 会像浏览器卸载页面一样自动帮忙清掉。

正解RN 不会自动移除监听。必须在组件卸载(componentWillUnmount 或 useEffect cleanup)中手动调用 removeListener / removeAllListeners,否则监听器残留在事件表里造成内存泄漏。

为何常见:前端开发者习惯了浏览器事件监听随页面销毁自动回收,迁移到 RN 时容易沿用错误的心智模型。

误区TurboModule 就是把旧模块换了个名字,接口看起来差不多,性能提升是宣传话术。

正解TurboModule 通过 Codegen 生成类型安全接口、懒加载模块并缓存常驻引用,消除了旧 Bridge 每次调用都进行的全局动态查找和装箱拆箱,调用时延普遍下降 40% 以上。名字没变的背后,调用链路已经完全不同。

为何常见:从外部 API 看新旧写法差异不大,只有深入序列化和调用链路才能感受到内部实现的质变。

面试题

Q1. RN 传统架构中,JS 与原生通信的主要通道是?

单选题 · easy

  • A.HTTP 请求通道
  • B.Bridge 消息桥接← 正确答案
  • C.WebSocket 通道
  • D.文件读写通道

答案:B。Bridge 是 RN 中 JS 与原生之间的唯一消息通道,像“跨洋邮局”一样承担全部跨端通信。HTTP、WebSocket 是网络层能力,文件读写则与跨端调用无关。

Q2. Bridge 消息采用哪种序列化格式?

单选题 · easy

  • A.可扩展标记语言格式
  • B.轻量级数据交换格式← 正确答案
  • C.紧凑型二进制数据格式
  • D.纯文本自定义传输格式

答案:B。Bridge 消息使用 JSON(JavaScript Object Notation)序列化,也就是题干中的“轻量级数据交换格式”。它跨语言、易读写,但也因此带来序列化与反序列化的 CPU 开销。

Q3. 传统 Bridge 中 JS 调用原生方法的发送方式是?

单选题 · mid

  • A.实时逐条立即发送
  • B.队列批量异步发送← 正确答案
  • C.同步直连等待返回
  • D.共享内存直接写入

答案:B。传统 Bridge 的消息通过 MessageQueue 攒批后统一发送,属于队列批量异步模型。实时逐条、同步直连、共享内存都不是传统桥接的行为,后两者是 JSI 时代才具备的能力。

Q4. 关于 NativeEventEmitter 的事件订阅机制,下列说法正确的是?

多选题 · mid

  • A.组件卸载时监听自动清理
  • B.注册监听函数接收事件← 正确答案
  • C.事件监听器需要手动释放← 正确答案
  • D.事件名称必须保证全局唯一

答案:BC。addListener 负责注册监听,removeListener 负责手动释放,二者必须成对出现,因此 B、C 正确。RN 不会在组件卸载时自动清理监听,A 错误;不同原生模块可以发出同名事件,事件名并不要求全局唯一,D 错误。

Q5. 传统 Bridge 通信中导致延迟的主要架构原因是?

单选题 · mid

  • A.消息反复序列化与排队← 正确答案
  • B.原生系统 API 接口太少
  • C.JS 引擎运行速度过慢
  • D.网络请求带宽不够用

答案:A。传统 Bridge 每次通信都要经历 JSON 序列化、队列排队、线程切换与反序列化,这正是正文时延模型的四个环节。API 数量、JS 引擎速度、网络带宽都不是桥接延迟的核心来源。

Q6. RN 新架构中 JSI 的中文全称是?

单选题 · easy

  • A.通用标准交互接口
  • B.脚本引擎运行接口
  • C.原生交互接口层← 正确答案
  • D.序列化通信接口

答案:C。JSI 的全称是 JavaScript Interface,即 JavaScript 与原生 C++ 对象之间交互的接口层。它让 JS 可以直接持有并调用原生对象,免去 JSON 序列化,是“直拨专线”式的能力接口。

Q7. TurboModule 带来哪些具体改进?

多选题 · hard

  • A.代码生成器产出安全接口← 正确答案
  • B.模块懒加载减少启动成本← 正确答案
  • C.常驻引用消除重复查找← 正确答案
  • D.完全移除消息队列传参

答案:ABC。TurboModule 通过 Codegen 生成类型安全接口、支持模块懒加载、以常驻引用消除每次调用的动态查找,因此 A、B、C 正确。它大幅减少了序列化和队列传参,但并未完全移除消息队列,D 过于绝对。

Q8. JSI 相比传统 Bridge 的核心突破是什么?

单选题 · mid

  • A.增加消息队列长度上限
  • B.免序列化直接调用原生← 正确答案
  • C.改用二进制消息提速
  • D.编译原生代码为 JS

答案:B。JSI 的核心突破是让 JS 直接持有原生对象引用并调用方法,序列化开销趋近于零。它改变了通信模型本身,而不是在原有模型上做参数调整,更不是把原生代码编译成 JS。

Q9. 移除 NativeEventEmitter 监听的主要作用是避免什么?

单选题 · hard

  • A.事件名冲突导致分发错乱
  • B.监听器残留造成内存泄漏← 正确答案
  • C.桥接队列消息重复发送
  • D.原生模块重复初始化

答案:B。未移除的监听器会残留在事件表中,形成“幽灵订阅”,导致回调对象无法被回收、内存持续增长。这正是 removeListener 必须与 addListener 成对出现的原因。事件名冲突、消息重复发送、模块重复初始化都与监听释放无关。

Q10. 关于 JSI 调用能力的说法正确的是?

多选题 · hard

  • A.JS 可以同步调用原生函数← 正确答案
  • B.原生对象引用可被 JS 直接持有← 正确答案
  • C.消息仍必须经 JSON 序列化
  • D.C++ 对象可与 JS 共享内存← 正确答案

答案:ABD。JSI 支持同步与异步两种调用方式,JS 可以直接持有 C++ 对象引用,并实现内存级共享,因此 A、B、D 正确。JSI 的最大突破恰恰是取消了 JSON 序列化,C 描述的是传统 Bridge 的模型,与 JSI 相反。