JSI入门指南
前言
要想彻底理解React Native新架构,JSI是绕不过去的槛。所以本文作为React Native源码剖析的第二篇文章。阅读本文需要了解一些简单的现代C++知识。新架构RN基本上是基于C++ 17开发的。学习现代C++可以极大的扩宽知识边界,提升能力范围。而且现代C++更加规范,学习难度降低,是性价比极高的选择。当然,仅作为源码阅读的需要,并不用深入学习C++,甚至不用会写代码,只要能看懂一些语法足够了。如果想更进一步,学习基本的现代C++开发,可以帮助我们开发React Native的纯C++ TurboModule,可以极大的优化RN App的性能,以及应用范围,在后面的RN新架构的一些列介绍文章中,相信对这一点一定体会极深。
JSI 概述
什么是JSI?
JSI(JavaScript Interface) 是 React Native 新架构中的一个核心组件,本质上是一个由 C++ 实现的轻量级接口层,用来连接:
- 一边是 JavaScript 运行时(如 Hermes、V8)
- 另一边是原生代码(主要是 C++,间接连到 Java/Obj‑C/平台 API)
它的目标是取代旧的异步 Bridge(JSON 消息桥) ,让 JS 与原生之间可以 直接、高性能地互相调用,大幅降低通信开销和延迟。
简单说,以前 JS 和原生要靠发 JSON 消息来通信,现在通过 JSI,双方可以像直接函数调用那样对话,并且可以互相保存对方对象的引用。
JSI 的核心特点
-
直接、低开销的 JS ↔ 原生通信
-
JSI 允许 JavaScript 保存 C++ 对象引用,C++ 也能保存 JS 对象引用,通过内存引用直接调用方法
-
不再需要把数据转成 JSON 再跨线程传递,去掉序列化/反序列化开销,尤其对大数据量(如相机帧缓冲、图像、音频样本等)非常关键
-
性能密集型库(例如 VisionCamera、Reanimated、Skia 等)借助 JSI 才能在 React Native 里做到接近原生的实时性能
-
-
支持同步和异步调用
-
旧 Bridge 只能异步,很多场景需要"立刻拿到结果"很别扭。
-
JSI 提供:
-
异步调用:常规推荐方式,避免阻塞 JS 线程或 UI 线程。
-
同步调用:在确实需要"立即返回值"的场景(例如获取剪贴板、当前位置等)可以直接从原生拿值返回给 JS,而不用 Promise / 回调
-
这种"既支持同步又支持异步"的模式,让 React Native 在易用性和性能之间有了更多空间。
-
-
-
用 C++ 实现、为性能而生
-
JSI 自身是 C++ API ,提供
jsi::Runtime、jsi::Object、jsi::Function、jsi::Value等低层接口,用于:- 在原生代码中操作 JS 值与对象;
- 注册可供 JS 调用的原生函数;
- 直接与 JS 引擎交互
- 由于是本地编译代码,减少了中间层和解释开销,带来更好的 启动时间与运行时性能
-
-
脱离旧 Bridge,成为新架构的基础。React Native 新架构的几个关键词:JSI、TurboModules、Fabric、Codegen,其中 JSI 是整个系统的"底座"
-
TurboModules:新一代原生模块系统,通过 JSI 实现 JS 与原生模块的直接调用,而不是通过老 Bridge
-
Fabric 渲染器:UI 事件与渲染更新通过 JSI 与 JS 运行时通信,使得界面更新更高效、更可控
-
Codegen:从类型化的 JS/TS 声明自动生成原生 C++/平台代码,这些代码通过 JSI 与 JS 通信
-
JSI 还是一个 与 JS 引擎无关的接口层,因此可以支持 Hermes、V8 等多种引擎,不再绑定于 JavaScriptCore
-
-
更适合高性能与跨平台原生模块。借助 JSI,你可以:
- 写 纯 C++ 模块,然后在 Android 和 iOS 上复用这一套实现,只需很少的平台胶水代码
- 直接在原生层访问设备能力(相机、蓝牙、GPS 等),并暴露给 JS 使用,同时保持高性能
- 将复杂实例对象(数据库连接、图像缓冲、音频样本等)直接暴露给 JS 层,而不是一堆 JSON 数据
JSI vs Bridge
| 特性 | Bridge (旧架构) | JSI (新架构) |
|---|---|---|
| 通信方式 | JSON 序列化/反序列化 | 直接内存访问 |
| 性能 | 慢(每次调用都序列化) | 快(零序列化开销) |
| 同步调用 | ❌ 不支持 | ✅ 支持 |
| 类型安全 | ❌ 运行时检查 | ✅ C++ 类型系统 |
| 内存开销 | 高(JSON 字符串) | 低(直接引用) |
JSI 类型系统
类型层级结构
typescript
ICast (接口)
└─ Runtime (抽象类)
Pointer (基类 - 不可拷贝,可移动)
├─ PropNameID (属性名)
├─ Symbol (ES6 Symbol)
├─ BigInt (大整数)
├─ String (字符串)
└─ Object (对象)
├─ Array (数组)
├─ ArrayBuffer (二进制缓冲区)
└─ Function (函数)
Value (联合类型 - 可存储任意 JS 值)
├─ undefined
├─ null
├─ boolean
├─ number
├─ Symbol
├─ BigInt
├─ String
└─ Object
Runtime - JS 引擎抽象
cpp
class Runtime : public ICast {
public:
// 1. 执行 JavaScript 代码
virtual Value evaluateJavaScript(
const std::shared_ptr<const Buffer>& buffer,
const std::string& sourceURL) = 0;
// 2. 创建 JS 对象
virtual Object createObject() = 0;
virtual Array createArray(size_t length) = 0;
virtual Function createFunctionFromHostFunction(
const PropNameID& name,
unsigned int paramCount,
HostFunctionType func) = 0;
// 3. 访问全局对象
virtual Object global() = 0;
// 4. 属性操作
virtual Value getProperty(const Object&, const PropNameID& name) = 0;
virtual void setPropertyValue(
const Object&, const PropNameID& name, const Value& value) = 0;
// 5. 微任务队列管理
virtual void queueMicrotask(const Function& callback) = 0;
virtual bool drainMicrotasks(int maxMicrotasksHint = -1) = 0;
};
引擎的具体实现(如:Hermes、JSC、V8)
cpp
// Hermes 实现示例
class HermesRuntime : public Runtime {
public:
Value evaluateJavaScript(...) override {
// Hermes 特定的 JS 执行逻辑
}
Object createObject() override {
// 调用 Hermes API 创建对象
}
};
Value - 通用 JS 值类型
cpp
class Value {
private:
enum ValueKind {
UndefinedKind,
NullKind,
BooleanKind,
NumberKind,
SymbolKind,
BigIntKind,
StringKind,
ObjectKind,
};
union Data {
bool boolean;
double number;
Pointer pointer; // Symbol/String/Object
};
ValueKind kind_;
Data data_; // 8 字节(64 位)
};
类型检查与转换
cpp
void processValue(Runtime& runtime, const Value& value) {
if (value.isUndefined()) {
// 处理 undefined
} else if (value.isNull()) {
// 处理 null
} else if (value.isBool()) {
bool b = value.getBool(); // 断言检查
bool b2 = value.asBool(); // 抛出异常
} else if (value.isNumber()) {
double d = value.getNumber();
} else if (value.isString()) {
String str = value.getString(runtime);
std::string utf8 = str.utf8(runtime);
} else if (value.isObject()) {
Object obj = value.getObject(runtime);
}
}
Object - JS 对象
创建对象
cpp
// 1. 空对象
Object obj(runtime); // 等价于 JS: {}
// 2. 带原型的对象
Value proto = runtime.global().getProperty(runtime, "MyPrototype");
Object obj = Object::create(runtime, proto);
// 3. HostObject(C++ 对象)
class MyHostObject : public HostObject {
Value get(Runtime& rt, const PropNameID& name) override {
if (name.utf8(rt) == "value") {
return Value(42);
}
return Value::undefined();
}
};
auto ho = std::make_shared<MyHostObject>();
Object obj = Object::createFromHostObject(runtime, ho);
属性操作
cpp
Object obj(runtime);
// 设置属性(支持多种类型)
obj.setProperty(runtime, "name", "John"); // const char*
obj.setProperty(runtime, "age", 30); // int
obj.setProperty(runtime, "active", true); // bool
// 获取属性
Value name = obj.getProperty(runtime, "name");
if (name.isString()) {
std::string str = name.getString(runtime).utf8(runtime);
}
// 检查属性
if (obj.hasProperty(runtime, "age")) {
// 属性存在
}
// 删除属性
obj.deleteProperty(runtime, "age");
Function - JS 函数
创建 C++ 函数供 JS 调用
cpp
// 定义 C++ lambda函数
auto myFunc = [](Runtime& runtime,
const Value& thisVal,
const Value* args,
size_t count) -> Value {
// 参数校验
if (count < 2) {
throw JSError(runtime, "Expected 2 arguments");
}
if (!args[0].isNumber() || !args[1].isNumber()) {
throw JSError(runtime, "Arguments must be numbers");
}
// 执行逻辑
double sum = args[0].getNumber() + args[1].getNumber();
return Value(sum);
};
// 注册到全局对象
auto funcName = PropNameID::forAscii(runtime, "myAdd");
Function func = Function::createFromHostFunction(
runtime, funcName, 2, myFunc);
runtime.global().setProperty(runtime, "myAdd", func);
// JS 中调用:
// const result = myAdd(10, 20); // 30
从 C++ 调用 JS 函数
cpp
// 获取 JS 函数
Value callback = obj.getProperty(runtime, "onClick");
if (callback.isObject() && callback.getObject(runtime).isFunction(runtime)) {
Function func = callback.getObject(runtime).getFunction(runtime);
// 方式 1:无 this,传递参数
Value result = func.call(runtime, Value(10), Value(20));
// 方式 2:带 this 上下文
Object thisObj(runtime);
Value result2 = func.callWithThis(runtime, thisObj, Value(10));
// 方式 3:作为构造函数调用
Value instance = func.callAsConstructor(runtime, Value("arg1"));
}
Array - JS 数组
cpp
// 创建数组
Array arr = Array::createWithElements(runtime, 1, 2, "hello", true);
// 访问元素
size_t length = arr.size(runtime);
for (size_t i = 0; i < length; i++) {
Value element = arr.getValueAtIndex(runtime, i);
}
// 修改元素
arr.setValueAtIndex(runtime, 0, Value(100));
// 转换为普通 Object
Object obj = arr.asObject(runtime); // 类型安全转换
JS与C++的调用机制
HostObject
HostObject是一个非常重要的概念,它的作用就是将一个C++对象直接暴露给JS层使用。
更具体的说:
HostObject是一个 C++ 类 ,完整的类是facebook::jsi::HostObject- 你可以通过它把原生对象(例如图片、存储、数据库连接等)暴露给 JS
- JS 访问它的属性和方法时,看起来就像在用普通的 JS 对象
如何使用HostObject?
1.在 C++ 中定义 HostObject:
cpp
class NativeStorage : public facebook::jsi::HostObject {
public:
int expirationTime = 60 * 60 * 24; // 默认 1 天
// 读属性:nativeStorage.xxx
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override {
auto prop = name.utf8(runtime);
if (prop == "expirationTime") {
return jsi::Value(expirationTime);
}
// 也可以在这里返回"方法",例如 setObject / object(见后文)
// 否则:
return jsi::Value::undefined();
}
// 写属性:nativeStorage.xxx = ...
void set(jsi::Runtime& runtime, const jsi::PropNameID& name, const jsi::Value& value) override {
auto prop = name.utf8(runtime);
if (prop == "expirationTime" && value.isNumber()) {
expirationTime = (int)value.asNumber();
}
}
};
这里get方法相当于在定义:JS 中访问某个属性名时,底层到底要操作哪个 C++ 字段或执行什么逻辑。
2.把 HostObject 实例挂到 JS 运行时
cpp
void NativeStorage::install(jsi::Runtime& runtime) {
// 创建NativeStorage对象
auto instance = std::make_shared<NativeStorage>();
// 再从 HostObject 创建 JS 对象
auto object = jsi::Object::createFromHostObject(runtime, instance);
// 挂到 global 上,供 JS 使用:global.nativeStorage
runtime.global().setProperty(runtime, "nativeStorage", object);
}
这个过程可以称为安装,在适当的时机(通常是 JS runtime 已经创建好之后),调用这个 install 就行。
3.在 JS 侧使用
js
// 属性读写:对应 C++ 中 get/set 覆写
nativeStorage.expirationTime = 1000;
console.log(nativeStorage.expirationTime); // -> 1000
从 JS 视角看,这就是一个普通对象;从 C++ 视角看,它是一个持有原生资源和逻辑的类实例。
4.添加方法
HostObject 不只可以暴露数据属性,还可以在 get() 中给某个属性名返回一个 HostFunction ,这样这个属性在 JS 中就是一个 可调用方法。
例如在上面 NativeStorage 的基础上,给它加上 setObject / object 方法:
cpp
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override {
auto prop = name.utf8(runtime);
if (prop == "expirationTime") {
return jsi::Value(expirationTime);
}
if (prop == "setObject") {
return jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "setObject"),
2, // 参数个数:key, value
[](jsi::Runtime& rt, const jsi::Value& thisVal,
const jsi::Value* args, size_t count) -> jsi::Value {
// 这里做参数转换 + 原生存储逻辑
// 比如用 NSUserDefaults 或 SharedPreferences 等
return jsi::Value(true);
}
);
}
if (prop == "object") {
return jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "object"),
1,
[](jsi::Runtime& rt, const jsi::Value& thisVal,
const jsi::Value* args, size_t count) -> jsi::Value {
// 这里从原生存储中读取并返回
// return jsi::String::createFromUtf8(rt, ...);
return jsi::Value::undefined();
}
);
}
return jsi::Value::undefined();
}
那么在JS 层就可以这样使用:
js
nativeStorage.setObject('greeting', 'Hello JSI!');
const text = nativeStorage.object('greeting');
C++ 调用JS
调用 JS 函数,大概可以分两种情况,分别是回调函数和Promise 交互
回调函数
cpp
void registerClickHandler(Runtime& runtime) {
// 获取 JS 回调
Value onClickValue = runtime.global().getProperty(runtime, "onClick");
if (!onClickValue.isObject()) {
throw JSError(runtime, "onClick is not defined");
}
Object onClickObj = onClickValue.getObject(runtime);
if (!onClickObj.isFunction(runtime)) {
throw JSError(runtime, "onClick is not a function");
}
Function onClick = onClickObj.getFunction(runtime);
// C++ 事件触发时调用
// 必须在 JS 线程执行
onClick.call(runtime,
String::createFromAscii(runtime, "button1"),
Value(100), // x
Value(200)); // y
}
Promise 交互
cpp
Value createPromise(Runtime& runtime) {
// 获取 Promise 构造函数
Object promiseConstructor = runtime.global()
.getPropertyAsObject(runtime, "Promise");
// 创建 executor 函数
auto executor = [](Runtime& rt, const Value&, const Value* args, size_t count) {
Function resolve = args[0].getObject(rt).getFunction(rt);
Function reject = args[1].getObject(rt).getFunction(rt);
// 模拟异步操作
std::thread([&rt, resolve = std::move(resolve)]() mutable {
std::this_thread::sleep_for(std::chrono::seconds(1));
// 实际需要 MessageQueue 调度
resolve.call(rt, Value(42));
}).detach();
return Value::undefined();
};
Function executorFunc = Function::createFromHostFunction(
runtime,
PropNameID::forAscii(runtime, "executor"),
2,
executor);
// 调用 new Promise(executor)
return promiseConstructor.asFunction(runtime)
.callAsConstructor(runtime, executorFunc);
}
访问 JS 对象属性
cpp
void opObject(Runtime& runtime, const Object& obj) {
// 1. 获取所有属性名
Array propNames = obj.getPropertyNames(runtime);
size_t length = propNames.size(runtime);
std::cout << "Object properties:" << std::endl;
for (size_t i = 0; i < length; i++) {
Value nameValue = propNames.getValueAtIndex(runtime, i);
if (nameValue.isString()) {
std::string name = nameValue.getString(runtime).utf8(runtime);
// 2. 获取属性值
Value propValue = obj.getProperty(runtime, name.c_str());
// 3. 类型判断
std::string type;
if (propValue.isUndefined()) type = "undefined";
else if (propValue.isNull()) type = "null";
else if (propValue.isBool()) type = "boolean";
else if (propValue.isNumber()) type = "number";
else if (propValue.isString()) type = "string";
else if (propValue.isObject()) type = "object";
std::cout << " " << name << ": " << type << std::endl;
}
}
}
线程安全
由于JSI并不是线程安全的,如果直接在子线程调用JSI的相关接口,会导致闪退。因此,在使用JSI时,线程安全问题十分重要,必须谨慎。
jsi::Runtime的实现(Hermes/JSC/V8)其内部状态、GC、对象分配都不是线程安全的- 必须保证所有 JSI 操作在单一 JS 线程上串行执行
- 跨线程访问会导致数据竞争、内存损坏、崩溃
我们来看一下JSI提供的解决方案,源码react-native/packages/react-native/ReactCommon/callinvoker/ReactCommon/CallInvoker.h:
cpp
class CallInvoker {
public:
// 异步调度到 JS 线程(最常用)
virtual void invokeAsync(std::function<void(jsi::Runtime&)>&& func) noexcept = 0;
// 同步调用(阻塞当前线程直到 JS 线程执行完成)
virtual void invokeSync(std::function<void(jsi::Runtime&)>&& func) = 0;
virtual ~CallInvoker() = default;
};
也就是说,我们必须通过invokeAsync把执行相关JSI操作的闭包发送到JS线程执行。这里的invokeAsync可以从任意线程安全调用,不阻塞JS线程。注意,JS线程的阻塞,会直接导致UI的卡顿。
接下来,我们看一个结合Promise的JSI异步线程处理的完整示例:
cpp
#include <jsi/jsi.h>
#include <ReactCommon/CallInvoker.h>
#include <thread>
#include <chrono>
using namespace facebook::jsi;
class NetworkModule : public jsi::HostObject {
private:
std::shared_ptr<CallInvoker> jsInvoker_;
public:
NetworkModule(std::shared_ptr<CallInvoker> jsInvoker)
: jsInvoker_(std::move(jsInvoker)) {}
Value get(Runtime& runtime, const PropNameID& name) override {
auto methodName = name.utf8(runtime);
if (methodName == "fetchAsync") {
return unction::createFromHostFunction(
runtime,
name,
1,
[this](Runtime& rt, const Value&, const Value* args, size_t count) -> Value {
std::string url = args[0].getString(rt).utf8(rt);
// 1. 获取 Promise 构造函数
Object promiseConstructor = rt.global().getPropertyAsObject(rt, "Promise");
// 2. 创建 executor 函数
auto executor = Function::createFromHostFunction(
rt,
PropNameID::forAscii(rt, "executor"),
2,
[this, url](Runtime& runtime, const Value&, const Value* args, size_t) -> Value {
// 3. 保存 resolve/reject(使用 shared_ptr 延长生命周期)
auto resolve = std::make_shared<Function>(
args[0].getObject(rt).getFunction(rt));
auto reject = std::make_shared<Function>(
args[1].getObject(rt).getFunction(rt));
// 4. 后台线程执行
std::thread([this, resolve, reject, url]() {
try {
// 模拟网络请求
std::this_thread::sleep_for(std::chrono::seconds(1));
std::string result = "Response from " + url;
// 5. 调度到 JS 线程
jsInvoker_->invokeAsync([resolve, result](Runtime& rt) {
// createFromUtf8 这类JSI API必须在JS线程执行
resolve->call(rt, String::createFromUtf8(rt, result));
});
} catch (const std::exception& e) {
jsInvoker_->invokeAsync([reject, msg = std::string(e.what())](Runtime& rt) {
reject->call(rt, String::createFromUtf8(rt, msg));
});
}
}).detach();
return Value::undefined();
});
// 6. 返回 new Promise(executor)
return promiseConstructor.asFunction(rt).callAsConstructor(rt, executor);
});
}
return Value::undefined();
}
};
这样,上层JS 调用fetchAsync方法时,就会得到一个Promise对象,直到底层的子线程执行完任务后,将结果返回,上层Promise才会返回结果。整个耗时操作都由底层C++线程完成,不会阻塞JS线程,在整个耗时任务期间,JS线程都可以继续执行其他任务。
一些工具类
关于Promise 辅助类的使用。可以查看头文件react-native/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleUtils.h:
cpp
namespace facebook::react {
struct Promise : public LongLivedObject {
Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject);
void resolve(const jsi::Value &result);
void reject(const std::string &message);
jsi::Function resolve_;
jsi::Function reject_;
};
using PromiseSetupFunctionType = std::function<void(jsi::Runtime &rt, std::shared_ptr<Promise>)>;
jsi::Value createPromiseAsJSIValue(jsi::Runtime &rt, PromiseSetupFunctionType &&func);
}
使用示例:
cpp
// 在 HostFunction 中返回 Promise
return createPromiseAsJSIValue(rt, [jsInvoker](Runtime& rt, std::shared_ptr<Promise> promise) {
// 异步操作
std::thread([jsInvoker, promise]() {
// 后台工作...
// 完成后调度到 JS 线程
jsInvoker->invokeAsync([promise](Runtime& rt) {
promise->resolve(Value(42)); // 或 promise->reject("error")
});
}).detach();
});
LongLivedObject 可以防止过早的内存回收:
cpp
class MyData : public LongLivedObject {
public:
MyData(Runtime& rt) : LongLivedObject(rt) {}
void done() {
allowRelease(); // 允许被回收
}
};
// 使用
auto data = std::make_shared<MyData>(runtime);
LongLivedObjectCollection::get(runtime).add(data); // 防止回收
// ... 使用 data
data->allowRelease(); // 不需要时记得释放
常见错误示例
错误 1:直接在后台线程调用 JSI
cpp
// ❌ 错误
std::thread([&runtime, callback]() {
callback.call(runtime, Value(42)); // CRASH!
}).detach();
// ✅ 正确
std::thread([jsInvoker, callback = std::move(callback)]() mutable {
jsInvoker->invokeAsync([callback = std::move(callback)](Runtime& rt) {
callback.call(rt, Value(42));
});
}).detach();
错误 2:在异步回调中直接使用 runtime 引用
cpp
// ❌ 错误:runtime 引用可能失效
void asyncOp(Runtime& runtime, Function callback) {
std::thread([&runtime, callback]() { // 引用捕获危险
jsInvoker->invokeAsync([&runtime, callback](Runtime&) {
callback.call(runtime, Value(42)); // runtime 可能已销毁
});
}).detach();
}
// ✅ 正确:使用 lambda 传入的 runtime
void asyncOp(Runtime& runtime, Function callback, std::shared_ptr<CallInvoker> jsInvoker) {
jsInvoker->invokeAsync([callback = std::move(callback)](Runtime& rt) {
callback.call(rt, Value(42)); // 使用 lambda 参数rt
});
}
错误 3:忘记 Promise 生命周期管理
cpp
// ❌ 错误:Promise 可能被释放
auto promise = std::make_shared<Promise>(rt, resolve, reject);
std::thread([promise]() {
// Promise 可能被释放
}).detach();
// ✅ 正确:使用 createPromiseAsJSIValue(自动管理)
return createPromiseAsJSIValue(rt, [](Runtime& rt, std::shared_ptr<Promise> promise) {
// promise 已自动加入 LongLivedObjectCollection
});
关于createPromiseAsJSIValue函数,前面已经演示过了。
错误处理
看一个完整JSI错误处理示例:
cpp
Value safeCall(Runtime& runtime, const Function& func, const Value* args, size_t count) {
try {
return func.call(runtime, args, count);
} catch (const JSError& e) {
// JS 异常
std::cerr << "JS Error: " << e.getMessage() << std::endl;
std::cerr << "Stack: " << e.getStack() << std::endl;
throw;
} catch (const JSINativeException& e) {
// JSI 原生异常
std::cerr << "Native Error: " << e.what() << std::endl;
throw;
} catch (const std::exception& e) {
// 其他 C++ 异常
std::cerr << "C++ Error: " << e.what() << std::endl;
throw JSError(runtime, e.what());
}
}
追踪详细的异常栈:
cpp
void executeWithStackTrace(Runtime& runtime, const std::string& code) {
try {
runtime.evaluateJavaScript(
std::make_shared<StringBuffer>(code),
"debug.js");
} catch (const JSError& e) {
std::cerr << "=== JavaScript Error ===" << std::endl;
std::cerr << "Message: " << e.getMessage() << std::endl;
std::cerr << "Stack:\n" << e.getStack() << std::endl;
// 可以进一步解析堆栈
std::istringstream stream(e.getStack());
std::string line;
int frameNum = 0;
while (std::getline(stream, line)) {
std::cerr << " #" << frameNum++ << " " << line << std::endl;
}
}
}