JavascriptCore是使用在ReactNative和iOS平台上的Javascript引擎。
目前 JavaScript 引擎还有 Google 的 V8 ,Mozilla 的 SpiderMonkey。
目前 浏览器都用 大部分用 WebKit 内核做自己的引擎。
WebKit引擎
现在使用WebKit的主要两个浏览器Sfari和Chromium(Chorme的开源项目)。WebKit起源于KDE的开源项目Konqueror的分支,由苹果公司用于Sfari浏览器。其一条分支发展成为Chorme的内核,2013年Google在此基础上开发了新的Blink内核。
webkit是sfari、chrome等浏览器的排版引擎,各部分架构图如下
当然Android是同个类的是JavascriptInterace.
iOS
JavascriptCore
业界流行的动态化方案,如Facebook的RN,阿里的Weex 都采用了前端系的DSL方案,而它们在iOS系统上能够顺利的运行,都离不开一个背后的功臣:JavaScriptCore(以下简称JSCore),它建立起了OC和JS 两门语言之间沟通的桥梁。无论是这些流行的动态化方案,还是WebView Hybrid方案,亦或是之前广泛流行的JSPatch,JSCore都在其中发挥了举足轻重的作用。作为一名iOS开发工程师,了解JSCore已经逐渐成为了必备技能之一。
JavaScriptCore是一个优化的VM。 JavaScriptCore由以下构建块组成:词法分析器,解析器,启动解释器(LLInt),基线JIT,低延迟优化JIT(DFG)和高并发优化JIT(FTL)。
Lexer:词法分析器,生成 tokens,大部分代码都在 parser/Lexer.cpp 里。
Parser:语法分析,基于 Lexer 的 tokens 生成语法树。手写了个 recusive descent parser 递归下降解析器,代码主要在 parser/Parser.cpp 里。
LLInt:Low Level Interpreter 执行 Parser 生成的 Byte code。代码在 llint/ 里,使用汇编,在 offlineasm/ 里,可以编译为 x86 和 ARMv7 的汇编和 C 代码。LLInt 希望达成除了词法和语法分析外零启动消耗,同时遵守用 JIT 在调用,堆栈和起存器的约定。
Baseline JIT:实时编译,性能不好用这个。在函数调用了 6 次,或者某段代码循环了大于100次会被触发。BaseLine JIT 的代码在 jit/ 里。BaseLine JIT 还对几乎所有堆的访问执行了复杂的多态内联高速缓存(Polymorphic inline caches)。多态内联缓存是 Smalltalk 社区优化动态分发的一个经典技术。
DFG JIT:低延迟优化 JIT,更差性能就用这个生成更优化的机器码来执行。在函数被调用了60次或者代码循环了1000次会触发。在 LLInt 和 Baseline JIT 中会收集一些包括最近参数,堆以及返回值中的数据等轻量级的性能信息,方便 DFG 进行类型判断。先获取类型信息可以减少大量的类型检查,推测失败 DFG 会取消优化,也叫 OSR exit。取消可以是同步的也可以是异步的。取消后会回到 Baseline JIT,退回一定次数会进行重新优化,收集更多统计信息,看情况再次调用 DFG。重新优化使用的是指数式回退策略应对一些怪异的代码。DFG 代码在 dfg/ 里。
FTL:高吞吐量优化 JIT,全称 Faster Than Light,DFG 高层优化配合 B3 底层优化。以前全称是 Fourth Tier LLVM 底层优化使用的是 LLVM。B3 对 LLVM 做了裁剪,对 JavaScriptCore 做了特性处理,B3 IR 的接口和 LLVM IR 很类似。B3 对 LLVM 的替换主要是考虑减少内存开销,LLVM 主要是针对编译器,编译器在这方面优化动力必然没有 JIT 需求高。B3 IR 将指针改成了更紧凑的整数来表示引用关系。不可变的常用的信息使用固定大小的结构来表示,其它的都放到另外的地方。紧凑的数据放到数组中,更多的数组更少的链表。这样形成的 IR 更省内存。Filip Pizlo 主导的这个改动,DFG JIT 也是他弄的,为了能够更多的减少内存上的开销,他利用在 DFG 里已经做的 InsertionSet 将 LLVM IR 里的 def-use 干掉了,大概思路是把单向链表里批量插入新 IR 节点先放到 InsertionSet 里,在下次遍历 IR 时再批量插入。Filip Pizlo 还把 DFG 里的 UpsilonValue 替代 LLVM SSA 组成部分。B3 后面会把 LLVM 的寄存器分配算法 Greedy 一直到 B3 中。
再来看看JsCore的核心类
JSContext 代表JS的执行环境,通过-evaluateScript:方法就可以执行JS代码
JSValue 封装了JS与OC中的对应的类型,以及调用JS的API等
JSExport 是一个协议,遵守此协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来,才能调用
在 Native 中开启多个线程来异步执行不同API ,也就意味着开发者可创建多个 JSVirtualMachine VM,同时相互隔离不影响,这样保证了并行地执行不同 JS 任务。
在一个 JSVirtualMachine 中还可以关联多个 JSContext,并通过 JSValue( 来和 Native 进行数据传递通信,同时可以通过 JSExport,将 Native 中遵守此解析的类的方法和属性转换为 JS 的接口供其调用。
好了知道JsCore核心后,我们看看怎么使用。
总体来说,JavaScriptCore的API是非常简单的,主要操作步骤如下:
1.get一个JSContext。
_jsContext = [_webView valueForKeyPath: @"documentView.webView.mainFrame.javaScriptContext"];
- 处理解释某个JS 调起的方法,比如log
_jsContext[@"log"] = ^() { NSArray *args = [JSContext currentArguments]; for (id obj in args) {
//需要注意这里的obj都还是
JSValue NSLog(@"%@",obj);
} };
3.调用JS端执行某个JS方法
[_jsContext evaluateScript:@"log('arg1')"]; [_jsContext evaluateScript:@"logCallback('arg1')"];
4.重定义某个JS端的方法
[_jsContext evaluateScript:@"checkAPI = function(){\ return [\ 'selectImage',\ 'startRecord',\ 'login',\ ];\ }"];
ios 详细jsCore请移步:https://trac.webkit.org/wiki/JavaScriptCore
iOS与JS 交互的解决方案
比较著名的实现有基于jsCore的 JavascriptBridge。
https://github.com/marcuswestin/WebViewJavascriptBridge
当然我们自己也可以实现一个类似的框架
OC 里面执行 JS
比如我们算算我的年纪
JSContext *jsContext = [[JSContext alloc] init];
[jsContext evaluateScript:@"var name = tamic"];
[jsContext evaluateScript:@"var computeAge = function(value)
{ if (name == tamic) return 29 }"];
JSValue *value = [jsContext evaluateScript:@"computeAge(num)"];
int age = [value toInt32];
JS调用OC
//Block block其实就等于android 的接口回调,可以将代码块做参数传进去,以”getTamicName"为名传递给JavaScript上下文
JSContext *jsContext = [webView valueForKeyPath:
@"documentView.webView.mainFrame.javaScriptContext"];
jsContext[@"getTamicName"] = ^() {
NSArray *args = [JSContext currentArguments];
for (JSValue *obj in args) {
NSLog(@"%@", obj);
}
};
写在最后
到此我们学习Jscore的原理和js和OC的通讯, 下篇,我们接着讲Android中 的 JavascriptInterace。
更多我的技术文章 关注开发者技术前线公众号。