JavaScriptCore 框架:从使用到原理解析
JavaScript 越来越多地出现在我们客户端开发的视野中,从 React Native 到 JSPatch,JavaScript 与客户端相结合的技术开始变得魅力无穷。本文主要讲解 iOS 中的 JavaScriptCore 框架,正是它为 iOS 提供了执行 JavaScript 代码的能力。未来的技术日新月异,JavaScript 与 iOS 正在碰撞出新的激情。
JavaScriptCore 是 JavaScript 的虚拟机,为 JavaScript 的执行提供底层资源。
📋 目录
- 一、JavaScript
- 二、JavaScriptCore
- 三、JSVirtualMachine
- 四、JSContext
- 五、JSValue
- 六、JSExport 与原生对象导出
- 七、JSManagedValue 与内存管理
- 八、关键概念图示与流程
- 九、应用场景与最佳实践
- 十、伪代码与算法说明
- 参考文献
一、JavaScript
在讨论JavaScriptCore之前,我们首先必须对JavaScript有所了解。
1. JavaScript干啥的?
- 说的高大上一点:一门基于原型、函数先行的高级编程语言,通过解释执行,是动态类型的直译语言。是一门多范式的语言,它支持面向对象编程,命令式编程,以及函数式编程。
- 说的通俗一点:主要用于网页,为其提供动态交互的能力。可嵌入动态文本于HTML页面,对浏览器事件作出响应,读写HTML元素,控制cookies等。
- 再通俗一点:抢月饼,button.click()。(PS:请谨慎使用while循环)
2. JavaScript起源与历史
- 1990年底,欧洲核能研究组织(CERN)科学家Tim Berners-Lee,在互联网的基础上,发明了万维网(World Wide Web),从此可以在网上浏览网页文件。
- 1994年12月,Netscape 发布了一款面向普通用户的新一代的浏览器Navigator 1.0版,市场份额一举超过90%。
- 1995年,Netscape公司雇佣了程序员Brendan Eich开发这种嵌入网页的脚本语言。最初名字叫做Mocha,1995年9月改为LiveScript。
- 1995年12月,Netscape公司与Sun公司达成协议,后者允许将这种语言叫做JavaScript。
3. JavaScript与ECMAScript
- “JavaScript”是Sun公司的注册商标,用来特制网景(现在的Mozilla)对于这门语言的实现。网景将这门语言作为标准提交给了ECMA——欧洲计算机制造协会。由于商标上的冲突,这门语言的标准版本改了一个丑陋的名字“ECMAScript”。同样由于商标的冲突,微软对这门语言的实现版本取了一个广为人知的名字“Jscript”。
- ECMAScript作为JavaScript的标准,一般认为后者是前者的实现。
4. Java和JavaScript
《雷锋和雷峰塔》
Java 和 JavaScript 是两门不同的编程语言 一般认为,当时 Netscape 之所以将 LiveScript 命名为 JavaScript,是因为 Java 是当时最流行的编程语言,带有 “Java” 的名字有助于这门新生语言的传播。
二、 JavaScriptCore
1. 浏览器演进
- 演进完整图
upload.wikimedia.org/wikipedia/c…
- WebKit分支
现在使用WebKit的主要两个浏览器Sfari和Chromium(Chorme的开源项目)。WebKit起源于KDE的开源项目Konqueror的分支,由苹果公司用于Sfari浏览器。其一条分支发展成为Chorme的内核,2013年Google在此基础上开发了新的Blink内核。
2. WebKit排版引擎
webkit是sfari、chrome等浏览器的排版引擎,各部分架构图如下
- webkit Embedding API是browser UI与webpage进行交互的api接口;
- platformAPI提供与底层驱动的交互, 如网络, 字体渲染, 影音文件解码, 渲染引擎等;
- WebCore它实现了对文档的模型化,包括了CSS, DOM, Render等的实现;
- JSCore是专门处理JavaScript脚本的引擎;
3. JavaScript引擎
- JavaScript引擎是专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器之中。第一个JavaScript引擎由布兰登·艾克在网景公司开发,用于Netscape Navigator网页浏览器中。JavaScriptCore就是一个JavaScript引擎。
- 下图是当前主要的还在开发中的JavaScript引擎
4. JavaScriptCore组成
JavaScriptCore主要由以下模块组成:
- Lexer 词法分析器,将脚本源码分解成一系列的Token
- Parser 语法分析器,处理Token并生成相应的语法树
- LLInt 低级解释器,执行Parser生成的二进制代码
- Baseline JIT 基线JIT(just in time 实施编译)
- DFG 低延迟优化的JIT
- FTL 高通量优化的JIT
关于更多JavaScriptCore的实现细节,参考 trac.webkit.org/wiki/JavaSc…
5. JavaScriptCore 框架与历史
JavaScriptCore 是一个 C++ 实现的开源项目(WebKit 的一部分)。历史上,JSC 长期作为 Safari / WebKit 的内置 JS 引擎;自 iOS 7.0 / OS X 10.9 起,Apple 将 JavaScriptCore 以系统框架 JavaScriptCore.framework 的形式开放给开发者,使其可在 Objective-C 或基于 C 的程序中执行 JavaScript 代码,并向 JS 环境中插入自定义对象,而无需依赖 UIWebView。这为 Hybrid 应用、热更新、脚本引擎等场景提供了统一的底层能力。
在 JavaScriptCore.h 中,我们可以看到:
#ifndef JavaScriptCore_h
#define JavaScriptCore_h
#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/JSStringRefCF.h>
#if defined(__OBJC__) && JSC_OBJC_API_ENABLED
#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"
#endif
#endif /* JavaScriptCore_h */
这里已经很清晰地列出了JavaScriptCore的主要几个类:
- JSContext
- JSValue
- JSManagedValue
- JSVirtualMachine
- JSExport
接下来我们会依次讲解这几个类的用法。
6. Hello World!
这段代码展示了如何在 Objective-C 中执行一段 JavaScript 代码,并且获取返回值并转换成 OC 数据打印:
// 创建虚拟机
JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
//创建上下文
JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
//执行JavaScript代码并获取返回值
JSValue *value = [context evaluateScript:@"1+2*3"];
// 转换成 OC 数据并打印
NSLog(@"value = %d", [value toInt32]);
// Output: value = 7
Swift 等价写法:
import JavaScriptCore
let vm = JSVirtualMachine()!
let context = JSContext(virtualMachine: vm)!
let value = context.evaluateScript("1 + 2 * 3")!
print("value =", value.toInt32()) // value = 7
三、 JSVirtualMachine
一个JSVirtualMachine的实例就是一个完整独立的JavaScript的执行环境,为JavaScript的执行提供底层资源。
这个类主要用来做两件事情:
- 实现并发的 JavaScript 执行
- JavaScript 和 Objective-C 桥接对象的内存管理
看下头文件 JSVirtualMachine.h 里有什么:
NS_CLASS_AVAILABLE(10_9, 7_0)
@interface JSVirtualMachine : NSObject
/* 创建一个新的完全独立的虚拟机 */
(instancetype)init;
/* 对桥接对象进行内存管理 */
- (void)addManagedReference:(id)object withOwner:(id)owner;
/* 取消对桥接对象的内存管理 */
- (void)removeManagedReference:(id)object withOwner:(id)owner;
@end
每一个JavaScript上下文(JSContext对象)都归属于一个虚拟机(JSVirtualMachine)。每个虚拟机可以包含多个不同的上下文,并允许在这些不同的上下文之间传值(JSValue对象)。
然而,每个虚拟机都是完整且独立的,有其独立的堆空间和垃圾回收器(garbage collector ),GC无法处理别的虚拟机堆中的对象,因此你不能把一个虚拟机中创建的值传给另一个虚拟机。
线程和JavaScript的并发执行
JavaScriptCore API都是线程安全的。你可以在任意线程创建JSValue或者执行JS代码,然而,所有其他想要使用该虚拟机的线程都要等待。
- 如果想并发执行JS,需要使用多个不同的虚拟机来实现。
- 可以在子线程中执行JS代码。
通过下面这个 demo 来理解这个并发机制:
JSContext *context = [[CustomJSContext alloc] init];
JSContext *context1 = [[CustomJSContext alloc] init];
JSContext *context2 = [[CustomJSContext alloc] initWithVirtualMachine:[context virtualMachine]];
NSLog(@"start");
dispatch_async(queue, ^{
while (true) {
sleep(1);
[context evaluateScript:@"log('tick')"];
}
});
dispatch_async(queue1, ^{
while (true) {
sleep(1);
[context1 evaluateScript:@"log('tick_1')"];
}
});
dispatch_async(queue2, ^{
while (true) {
sleep(1);
[context2 evaluateScript:@"log('tick_2')"];
}
});
[context evaluateScript:@"sleep(5)"];
NSLog(@"end");
context和context2属于同一个虚拟机。
context1属于另一个虚拟机。
三个线程分别异步执行每秒1次的js log,首先会休眠1秒。
在context上执行一个休眠5秒的JS函数。
首先执行的应该是休眠5秒的JS函数,在此期间,context所处的虚拟机上的其他调用都会处于等待状态,因此tick和tick_2在前5秒都不会有执行。
而context1所处的虚拟机仍然可以正常执行tick_1。
休眠5秒结束后,tick和tick_2才会开始执行(不保证先后顺序)。
实际运行输出的 log 是:
start
tick_1
tick_1
tick_1
tick_1
end
tick
tick_2
四、 JSContext
一个JSContext对象代表一个JavaScript执行环境。在native代码中,使用JSContext去执行JS代码,访问JS中定义或者计算的值,并使JavaScript可以访问native的对象、方法、函数。
1. JSContext执行JS代码
- 调用evaluateScript函数可以执行一段
top-level的JS代码,并可向global对象添加函数和对象定义 - 其返回值是JavaScript代码中最后一个生成的值
API Reference
NS_CLASS_AVAILABLE(10_9, 7_0)
@interface JSContext : NSObject
/* 创建一个JSContext,同时会创建一个新的JSVirtualMachine */
(instancetype)init;
/* 在指定虚拟机上创建一个JSContext */
(instancetype)initWithVirtualMachine:
(JSVirtualMachine*)virtualMachine;
/* 执行一段JS代码,返回最后生成的一个值 */
(JSValue *)evaluateScript:(NSString *)script;
/* 执行一段JS代码,并将sourceURL认作其源码URL(仅作标记用) */
- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL*)sourceURL NS_AVAILABLE(10_10, 8_0);
/* 获取当前执行的JavaScript代码的context */
+ (JSContext *)currentContext;
/* 获取当前执行的JavaScript function*/
+ (JSValue *)currentCallee NS_AVAILABLE(10_10, 8_0);
/* 获取当前执行的JavaScript代码的this */
+ (JSValue *)currentThis;
/* Returns the arguments to the current native callback from JavaScript code.*/
+ (NSArray *)currentArguments;
/* 获取当前context的全局对象。WebKit中的context返回的便是WindowProxy对象*/
@property (readonly, strong) JSValue *globalObject;
@property (strong) JSValue *exception;
@property (copy) void(^exceptionHandler)(JSContext *context, JSValue
*exception);
@property (readonly, strong) JSVirtualMachine *virtualMachine;
@property (copy) NSString *name NS_AVAILABLE(10_10, 8_0);
@end
2. JSContext访问JS对象
一个JSContext对象对应了一个全局对象(global object)。例如web浏览器中中的JSContext,其全局对象就是window对象。在其他环境中,全局对象也承担了类似的角色,用来区分不同的JavaScript context的作用域。全局变量是全局对象的属性,可以通过JSValue对象或者context下标的方式来访问。
示例代码:
JSValue *value = [context evaluateScript:@"var a = 1+2*3;"];
NSLog(@"a = %@", [context objectForKeyedSubscript:@"a"]);
NSLog(@"a = %@", [context.globalObject objectForKeyedSubscript:@"a"]);
NSLog(@"a = %@", context[@"a"]);
// Output: a = 7, a = 7, a = 7
这里列出了三种访问JavaScript对象的方法
- 通过context的实例方法objectForKeyedSubscript
- 通过context.globalObject的objectForKeyedSubscript实例方法
- 通过下标方式
设置属性也是对应的。
API Reference
/* 为 JSContext 提供下标访问元素的方式 */
@interface JSContext (SubscriptSupport)
/* 首先将key转为JSValue对象,然后使用这个值在JavaScript context的全局对象中查找这个名字的属性并返回 */
(JSValue *)objectForKeyedSubscript:(id)key;
/* 首先将key转为JSValue对象,然后用这个值在JavaScript context的全局对象中设置这个属性。
可使用这个方法将native中的对象或者方法桥接给JavaScript调用 */
(void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying>*)key;
@end
/* 例如:以下代码在JavaScript中创建了一个实现是Objective-C block的function */
context[@"makeNSColor"] = ^(NSDictionary *rgb){
float r = [rgb[@"red"] floatValue];
float g = [rgb[@"green"] floatValue];
float b = [rgb[@"blue"] floatValue];
return [NSColor colorWithRed:(r / 255.f) green:(g / 255.f) blue:(b / 255.f) alpha:1.0];
};
JSValue *value = [context evaluateScript:@"makeNSColor({red:12, green:23, blue:67})"];
五、 JSValue
一个JSValue实例就是一个JavaScript值的引用。使用JSValue类在JavaScript和native代码之间转换一些基本类型的数据(比如数值和字符串)。你也可以使用这个类去创建包装了自定义类的native对象的JavaScript对象,或者创建由native方法或者block实现的JavaScript函数。
每个JSValue实例都来源于一个代表JavaScript执行环境的JSContext对象,这个执行环境就包含了这个JSValue对应的值。每个JSValue对象都持有其JSContext对象的强引用,只要有任何一个与特定JSContext关联的JSValue被持有(retain),这个JSContext就会一直存活。通过调用JSValue的实例方法返回的其他的JSValue对象都属于与最始的JSValue相同的JSContext。
每个JSValue都通过其JSContext间接关联了一个特定的代表执行资源基础的JSVirtualMachine对象。你只能将一个JSValue对象传给由相同虚拟机管理(host)的JSValue或者JSContext的实例方法。如果尝试把一个虚拟机的JSValue传给另一个虚拟机,将会触发一个Objective-C异常。
1. JSValue类型转换
JSValue提供了一系列的方法将native与JavaScript的数据类型进行相互转换:
2. NSDictionary与JS对象
NSDictionary 对象以及其包含的 keys 与 JavaScript 中的对应名称的属性相互转换。key 所对应的值也会递归地进行拷贝和转换。
[context evaluateScript:@"var color = {red:230, green:90, blue:100}"];
//js->native 给你看我的颜色
JSValue *colorValue = context[@"color"];
NSLog(@"r=%@, g=%@, b=%@", colorValue[@"red"], colorValue[@"green"], colorValue[@"blue"]);
NSDictionary *colorDic = [colorValue toDictionary];
NSLog(@"r=%@, g=%@, b=%@", colorDic[@"red"], colorDic[@"green"], colorDic[@"blue"]);
//native->js 给你点颜色看看
context[@"color"] = @{@"red":@(0), @"green":@(0), @"blue":@(0)};
[context evaluateScript:@"log('r:'+color.red+'g:'+color.green+' b:'+color.blue)"];
// Output:
// r=230, g=90, b=100
// r=230, g=90, b=100
// r:0 g:0 b:0
可见,JS中的对象可以直接转换成Objective-C中的NSDictionary,NSDictionary传入JavaScript也可以直接当作对象被使用。
3. NSArray与JS数组
NSArray 对象与 JavaScript 中的 array 相互转换。其子元素也会递归地进行拷贝和转换。
[context evaluateScript:@"var friends = ['Alice','Jenny','XiaoMing']"];
//js->native 你说哪个是真爱?
JSValue *friendsValue = context[@"friends"];
NSLog(@"%@, %@, %@", friendsValue[0], friendsValue[1], friendsValue[2]);
NSArray *friendsArray = [friendsValue toArray];
NSLog(@"%@, %@, %@", friendsArray[0], friendsArray[1], friendsArray[2]);
//native->js 我觉得 XiaoMing 不错,给你再推荐个 Jimmy
context[@"girlFriends"] = @[friendsArray[2], @"Jimmy"];
[context evaluateScript:@"log('girlFriends :'+girlFriends[0]+' '+girlFriends[1])"];
// Output: Alice, Jenny, XiaoMing / girlFriends : XiaoMing Jimmy
4. Block/函数和JS function
Objective-C中的block转换成JavaScript中的function对象。参数以及返回类型使用相同的规则转换。
将一个代表native的block或者方法的JavaScript function进行转换将会得到那个block或方法。
其他的JavaScript函数将会被转换为一个空的dictionary。因为JavaScript函数也是一个对象。
5. OC对象和JS对象
对于所有其他 native 的对象类型,JavaScriptCore 都会创建一个拥有 constructor 原型链的 wrapper 对象,用来反映 native 类型的继承关系。默认情况下,native 对象的属性和方法并不会导出给其对应的 JavaScript wrapper 对象。通过 JSExport 协议可选择性地导出属性和方法。下面第六节对 JSExport 与原生对象导出做详细讲解。
六、JSExport 与原生对象导出
JSExport 是 JavaScriptCore 框架中的协议,用于将 Objective-C/Swift 的类(属性与方法)选择性导出给 JavaScript,使 JS 代码可以像调用普通对象一样调用原生对象 [1][2]。
6.1 作用与机制
- 遵循 JSExport 的协议中声明的属性和方法,会在将 native 对象注入到 JSContext(如
context[@"bridge"] = nativeObject)时,自动暴露为 JS 侧的属性和函数。 - 若类未实现 JSExport 或未在协议中声明,则对应属性/方法不会出现在 JS 中;这样可控制「桥接面」,避免暴露内部实现 [1][2]。
6.2 使用示例(概念)
@protocol MyPointExport <JSExport>
@property (nonatomic, assign) double x;
@property (nonatomic, assign) double y;
- (NSString *)description;
@end
@interface MyPoint : NSObject <MyPointExport>
@property (nonatomic, assign) double x;
@property (nonatomic, assign) double y;
@end
将 MyPoint 实例赋给 context[@"point"] 后,在 JS 中可访问 point.x、point.y 并调用 point.description()。
注意:若在 Block 或导出方法中再次使用 JSValue 或 JSContext,需注意线程与内存管理(见第七节 JSManagedValue)[1][2]。
Swift 中的等价写法(通过 JSContext 注入遵循 JSExport 的类):
import JavaScriptCore
@objc protocol PointExport: JSExport {
var x: Double { get set }
var y: Double { get set }
func description() -> String
}
class Point: NSObject, PointExport {
@objc var x: Double
@objc var y: Double
init(x: Double, y: Double) { self.x = x; self.y = y }
func description() -> String { "Point(\(x), \(y))" }
}
// 注入到 context
let context = JSContext()!
context.setObject(Point(x: 1, y: 2), forKeyedSubscript: "point" as NSString)
context.evaluateScript("point.x; point.description()")
6.3 与 Block 注入的对比
| 方式 | 适用场景 |
|---|---|
| context[@"fn"] = ^(id arg){ ... } | 单次或简单逻辑,直接暴露为 JS 函数 |
| JSExport 协议 + 原生对象 | 需要暴露多个方法/属性、保持对象身份与状态的「桥接对象」 |
七、JSManagedValue 与内存管理
7.1 为何需要 JSManagedValue
- JSValue 对 JSContext 有强引用;JSContext 又挂在 JSVirtualMachine 上。
- 若在 堆上的 OC 对象(如某 ViewController 的 property)中直接强引用 JSValue,而该 JSValue 通过某种方式(例如被注入到 context 的全局对象)又引用回该 OC 对象,会形成 OC ↔ JS 的循环引用,导致 Context 与 OC 对象均无法释放 [1][2]。
7.2 JSManagedValue 的职责
JSManagedValue 是 JSValue 的包装类,用于在「被 OC 堆对象持有」的场景下,以条件保留的方式引用 JS 值,并可与 JSVirtualMachine 的 addManagedReference:withOwner: / removeManagedReference:withOwner: 配合,让虚拟机在合适的时机断开或保留对 native 对象的引用,从而打破循环、避免 JSContext 无法释放 [1][2]。
7.3 使用要点(概念)
- 当需要把 JSValue(或从 JS 传回的函数/对象)存为 OC 对象的成员变量时,应使用 JSManagedValue 包装,并以 owner 注册到 JSVirtualMachine;在 owner 析构或不再需要时调用 removeManagedReference:withOwner: [1][2]。
- 仅临时在栈上使用 JSValue(如
evaluateScript的返回值在方法内使用后不再持有)时,一般无需 JSManagedValue。
八、关键概念图示与流程
8.1 VM、Context、Value 关系
flowchart TB
subgraph VM1[JSVirtualMachine 1]
C1[JSContext 1]
C2[JSContext 2]
end
subgraph VM2[JSVirtualMachine 2]
C3[JSContext 3]
end
C1 --> V1[JSValue]
C2 --> V2[JSValue]
C1 -.->|可传值| C2
C1 -.->|不可跨 VM| C3
同一 JSVirtualMachine 下多个 JSContext 可共享、传递 JSValue;不同 VM 之间不能传递 JSValue [3]。
8.2 JavaScriptCore 引擎执行层级(概念)
源码经 Lexer → Parser 得到语法树并生成字节码后,由下至上的执行/编译层级可概括为:
flowchart LR
A[源码] --> B[Lexer]
B --> C[Parser / AST]
C --> D[字节码]
D --> E[LLInt 解释器]
E --> F[Baseline JIT]
F --> G[DFG JIT]
G --> H[FTL JIT]
- LLInt:低级解释器,低延迟启动。
- Baseline JIT:首次 JIT,兼顾分析与回退。
- DFG:基于数据流的优化 JIT。
- FTL:更高优化层(历史上曾用 LLVM/B3 后端)[4][5]。
更多实现细节见 WebKit JavaScriptCore Wiki。
九、应用场景与最佳实践
9.1 典型应用场景
| 场景 | 说明 |
|---|---|
| Hybrid 应用 | 在 App 内执行 JS 脚本、调用原生能力(如弹窗、定位、支付),JavaScriptCore 提供 OC/Swift 与 JS 的双向桥接 [1][2] |
| React Native / 类 RN 方案 | 早期 RN 等方案在 iOS 上依赖 JSC 执行 JS bundle;JSC 提供 VM、Context、Value 等能力 [3] |
| JSPatch 等热修复 | 通过下发 JS 脚本并在 JSC 中执行,动态调用原生类与方法,实现热更新(需注意安全与审核政策)[3] |
| WKWebView 与 Web 页面 | WKWebView 内部使用系统 WebKit,其 JS 引擎与 Safari 一致;独立使用 JSC 时无需 WebView 即可执行 JS [1][2] |
| 规则引擎 / 脚本配置 | 将业务规则或配置写成 JS,由原生在 JSC 中执行并取结果,便于迭代与 A/B 测试 |
9.2 最佳实践要点
- 线程:同一 VM 下多线程会串行等待;需并发执行 JS 时使用多个 JSVirtualMachine [3]。
- 异常:设置 context.exceptionHandler,在 JS 抛错时记录或上报,避免静默失败 [3]。
- 内存:在 OC 堆对象中持有 JS 值时使用 JSManagedValue + add/removeManagedReference,避免循环引用 [1][2]。
- 安全:执行来自网络或不可信来源的 JS 时,需做沙箱与权限控制;避免将敏感 API 无限制暴露给 JS [3]。
十、伪代码与算法说明
10.1 执行脚本并取返回值(概念)
function evaluateScript(script: String) -> JSValue:
parse script -> AST
generate bytecode from AST
execute bytecode (via LLInt or JIT tier)
return last expression value as JSValue
10.2 将 Native 对象注入 Context(概念)
function setObject(object: Any, forKey key: String):
if object is Block or conforms to JSExport:
create JS wrapper (function or object with exported properties/methods)
else:
create generic wrapper preserving native type hierarchy
set wrapper on context.globalObject[key]
10.3 JS 调用 Native Block 时(概念)
在 JavaScript 侧,调用通过 context[@"key"] 注入的 Block,与调用普通函数一致:
// 假设 Native 已注入:context["makeColor"] = ^(NSDictionary *rgb) { ... }
var color = makeColor({ red: 12, green: 23, blue: 67 });
底层流程(伪代码):
当 JS 调用 context 中注册的 Block 时:
1. JSC 将 JS 参数按类型转换为 OC 对象(NSNumber/NSString/NSDictionary/NSArray 等)
2. 调用 Block,传入转换后的参数
3. 将 Block 返回值按类型转换为 JSValue 并返回给 JS
参考文献
[1] Apple. JavaScriptCore Framework. iOS / macOS Developer Documentation.
[2] 掘金 / 博客. iOS 与 JS 交互开发知识总结、JavaScriptCore 初探 等.
[3] 本文原稿与常见 JSC 教程(JSVirtualMachine、JSContext、JSValue、并发与内存).
[4] WebKit. Introducing the WebKit FTL JIT. webkit.org/blog/3362/i…
[5] WebKit. JavaScriptCore - Deep Dive. docs.webkit.org/Deep%20Dive…
[6] trac.webkit.org. JavaScriptCore. trac.webkit.org/wiki/JavaSc…
[7] 美团技术团队. 深入理解 JSCore. blog.csdn.net/MeituanTech…