JSI(JavaScript Interface)是React Native中的一个软件层,它提供了从JavaScript到C++对象和函数的直接同步访问,取代了异步JSON桥Bridge。与其前身不同,JSI允许在不序列化消息的情况下调用原生方法,并直接将C++对象的引用传递到JS环境中。根据React Native团队(2025)的数据,JSI在密集数据交换的场景中,将JS与原生代码的交互速度提升高达10倍。
要点
JSI(JavaScript接口)是一个C++层,它为JavaScript引擎(Hermes、JavaScriptCore、V8)提供直接访问C++对象、函数和内存的能力。与Bridge(将调用序列化为JSON并通过异步队列传输)不同,JSI允许JS代码同步调用C++方法并立即获得结果。
JSI是在React Native 0.64中作为新架构(New Architecture)的一部分引入的。主要目标是消除Bridge这个瓶颈:JS与原生代码之间的每次交互都消耗时间在序列化、反序列化和通过消息队列传输上。JSI通过包装器(实现jsi::Value、jsi::Object和jsi::Function接口)为JS引擎提供对C++对象的直接访问来解决这个问题。
JSI不是Bridge的“一对一”替代品——这是一种根本不同的集成方法。Bridge像一个邮箱一样工作:JS发送一条消息,它经过队列,原生端处理它并发送回复。JSI像一个指针一样工作:JS接收到对C++对象的引用,并可以像普通JS函数一样同步调用其方法。这是两个环境交互架构中的根本区别。
对JSI的需求源于2015年React Native中原始Bridge的局限性。随着框架的流行度增长和应用程序复杂化,性能问题变得明显:每次原生模块调用至少需要3–5毫秒进行序列化。对于简单的操作,如读取传感器值或获取屏幕尺寸,这是可以接受的,但对于动画、图形处理和流式数据处理来说却是关键问题。React Native团队于2019年开始研究新架构,JSI成为其基础。
JSI被设计为JavaScript引擎之上的抽象层。它提供统一的C++ API,针对每个特定引擎实现:Hermes、JavaScriptCore(iOS)、V8(Android)。这意味着开发人员无需担心引擎之间的差异——Fabric和TurboModules无论使用哪种JS引擎都同样工作。
JSI的基础是Host Objects的概念——作为原生JS对象导出到JS环境中的C++对象。当JS代码访问此类对象的属性或方法时,JSI拦截调用并将其委托给相应的C++方法。这是在同一个线程中同步发生的,无需上下文切换,也无需为JSON字符串分配内存。
每个Host Object实现jsi::HostObject接口,包含get、set和getPropertyNames方法。JS引擎每次访问对象属性时都会调用这些方法。例如,在JS中调用NativeModule.someMethod()时,JSI将此调用转换为Host Object相应方法的C++调用。返回值作为jsi::Value传回JS——这是一种通用类型,可以表示数字、字符串、布尔值、对象或undefined。
JSI的一个重要特性是没有消息队列。Bridge使用异步队列:JS发送请求,切换到其他任务,原生端处理请求,结果通过回调返回。JSI同步工作:如果JS通过JSI调用原生模块的方法,JS代码的执行将暂停直到获得结果。这简化了逻辑(无需等待回调)并消除了竞态条件,但需要谨慎——长时间的同步调用会阻塞JS线程。
通过JSI创建的值存在于JS引擎的运行时环境中,并由垃圾回收器管理。当C++代码创建jsi::String或jsi::Object并将其返回给JS时,环境会自动管理内存。如果C++代码想在调用之间保留对JS值的引用,它会使用jsi::Value::getWeak()或全局jsi::Object::setProperty,在运行时根对象上保留引用。这可以防止垃圾回收器过早删除。
JSI默认不是线程安全的。所有JSI方法调用必须来自执行JS的线程(通常是React Native的JS线程)。如果原生模块在单独的线程中启动后台工作,结果必须通过JS线程使用TurboModules中的runOnJS调用返回。这个限制是同步性和缺乏序列化的代价。
JSI和Bridge之间的差异具有根本性,影响JS与原生代码交互的所有方面。Bridge是异步的,将数据序列化为JSON并使用消息队列;JSI是同步的,使用原生引用,不需要序列化。
| 参数 | Bridge | JSI |
|---|---|---|
| 调用模型 | 异步队列 | 同步直接调用 |
| 序列化 | JSON(序列化+反序列化) | 无(对C++对象的直接引用) |
| 延迟 | 每次调用3–10毫秒 | 每次调用0.1–0.5毫秒 |
| 类型系统 | 动态(通过JSON) | 静态(通过Codegen) |
| C++集成 | 仅通过原生模块(Java/ObjC) | 直接,无需中间层 |
| 线程 | 单独的原生线程 | JS线程(同步) |
根据React Native团队的数据,Facebook Marketplace应用从Bridge迁移到JSI后,启动时间减少了35%,内存消耗减少了20%,原因是消除了JS和原生端之间的数据重复。
Bridge不是“错误”——在2015年创建React Native时,这是一个合理的架构决策。为两种不同语言的平台进行原生开发需要一种通用的交换格式。JSON作为序列化格式在所有平台上都可用,并允许统一交互。问题后来才显现出来,当React Native开始用于每秒数千次原生模块调用的复杂应用时。
React Native保持向后兼容:为Bridge编写的原生模块通过兼容层继续在新架构中工作。但对于新模块,建议直接通过TurboModules使用JSI。现有模块的迁移涉及更改交互协议,而不改变模块自身的业务逻辑。
JSI是基础层,React Native新架构的所有组件都构建在其上。没有JSI,Fabric(新渲染器)和TurboModules(优化的原生模块)都无法工作。JSI在所有层级上提供JS与C++交互的统一方式。
Fabric——新的React Native渲染器,使用JSI同步访问C++中的UI表示。在旧架构中,渲染通过Bridge进行:JS创建React元素,序列化为JSON,通过Bridge发送,原生端反序列化并创建UI。Fabric通过JSI直接从JS创建C++ Shadow Tree对象,通过Yoga同步计算布局,并将准备好的帧传递给原生渲染器——无需任何序列化。
TurboModules——React Native原生模块的演进。TurboModules不是将模块注册到Bridge并通过JSON调用其方法,而是使用JSI进行懒加载和直接调用。当JS代码首次访问模块时,JSI创建一个Host Object——它加载原生模块并将其方法作为C++函数提供。懒加载意味着模块在首次使用前不消耗内存——这对于拥有数十个原生模块(其中许多仅在某些屏幕上使用)的应用程序尤其重要。
在新架构中使用JSI时,会用到Codegen——一个从JavaScript规范生成C++包装器的工具。开发人员用TypeScript或Flow描述原生模块的接口,Codegen生成实现与JSI兼容的Host Object的C++代码。这自动化了日常工作,并保证了JS和C++端的类型同步。
让我们看看实际中如何使用JSI。在这个示例中,创建了一个简单的C++类,通过JSI导出到JS,并从React Native JavaScript代码中调用其方法。
// Calculator.h — 可从JS访问的C++类头文件
class Calculator {
public:
double add(double a, double b) { return a + b; }
double multiply(double a, double b) { return a * b; }
};
Calculator类包含两个算术方法。我们需要使其可从JS访问。为此,我们创建一个Host Object来包装Calculator并通过JSI提供其方法。
// CalculatorHostObject.cpp — JSI包装器的实现
class CalculatorHostObject : public jsi::HostObject {
private:
Calculator calc;
public:
jsi::Value get(jsi::Runtime& runtime,
const jsi::PropNameID& name) override {
auto propName = name.utf8(runtime);
if (propName == "add") {
return jsi::Function::createFromHostFunction(
runtime, name, 2,
[this](jsi::Runtime& runtime,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) -> jsi::Value {
return jsi::Value(calc.add(
args[0].asNumber(),
args[1].asNumber()));
});
}
return jsi::Value::undefined();
}
};
在这段代码中,get方法在每次JS访问对象属性时被调用。如果属性名是“add”,则返回一个C++函数,该函数从JS接收两个参数并调用calc.add()。值作为jsi::Value返回——JSI自动将double转换为JS数字。
在JS环境中注册Host Object后,调用看起来就像普通的JS函数。所有类型在代码生成阶段进行检查,从而消除了运行时的类型不匹配错误。
// JavaScript — 通过JSI调用C++计算器
import { Calculator } from 'react-native-calculator'
const result = Calculator.add(5, 3)
console.log(result) // 8 — 同步,无延迟
const product = Calculator.multiply(4, 2.5)
console.log(product) // 10 — 即时结果
注意:结果立即返回,无需Promise、无需await、无需回调。这是一个同步调用,在Bridge架构中是不可能的。对于长时间运行的操作(读取文件、网络请求),应使用异步模式——JSI并不消除对后台线程处理繁重任务的需求。
在实践中,大多数开发人员不手动编写JSI Host Objects——这项工作由Codegen完成,它基于TypeScript规范生成C++包装器。然而,理解JSI在引擎盖下的工作原理对于有效调试性能和创建需要直接访问C++库(Skia、FFmpeg、OpenCV)的复杂原生模块是必要的。
常见问题
Bridge通过JSON序列化和消息队列异步工作——每次调用需要3–10毫秒进行数据转换。JSI提供对C++对象的同步直接访问,无需序列化,将延迟降低到0.1–0.5毫秒。JSI还支持传递对象引用,而不是它们的副本。
是的,JSI提供统一的C++ API,已为Hermes(React Native默认)、JavaScriptCore(iOS)和V8(Android)实现。开发人员无需为不同引擎编写不同代码——Fabric和TurboModules在所有支持的引擎上同样工作。
是的,React Native提供向后兼容层。为Bridge编写的原生模块继续在新架构中工作。但建议将它们迁移到TurboModules以获得JSI的优势——懒加载和同步调用。
对于日常开发——不需要。原生模块的TypeScript规范通过Codegen自动编译为C++包装器。只有在创建自己的C++库或在运行时级别调试JSI性能时才需要C++知识。
JSI解决了Bridge的三个关键问题:由于JSON序列化导致的高延迟、缺乏同步调用以及无法通过引用传递复杂对象。JSI还允许直接集成C++库,无需Java或Objective-C层。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。