JavaScriptCore全面解析 (上篇 转载于殷源的专栏订阅)

一、JavaScript

1. JavaScript干啥的?

2. JavaScript起源与历史

3. JavaScript与ECMAScript

4. Java和JavaScript

二、 JavaScriptCore

1. 浏览器演进

2. WebKit排版引擎

3. JavaScript引擎

4. JavaScriptCore组成

5. JavaScriptCore

6. Hello World!

三、 JSVirtualMachine

四、 JSContext

1. JSContext执行JS代码

2. JSContext访问JS对象

五、 JSValue

1. JSValue类型转换

2. NSDictionary与JS对象

3. NSArray与JS数组

4. Block/函数和JS function

5. OC对象和JS对象

JavaScript越来越多地出现在我们客户端开发的视野中,从ReactNative到JSpatch,JavaScript与客户端相结合的技术开始变得魅力无穷。本文主要讲解iOS中的JavaScriptCore框架,正是它为iOS提供了执行JavaScript代码的能力。未来的技术日新月异,JavaScript与iOS正在碰撞出新的激情。

JavaScriptCore是JavaScript的虚拟机,为JavaScript的执行提供底层资源。

一、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. 浏览器演进

演进完整图

https://upload.wikimedia.org/wikipedia/commons/7/74/Timeline_of_web_browsers.svg

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的实现细节,参考https://trac.webkit.org/wiki/JavaScriptCore

5. JavaScriptCore

JavaScriptCore是一个C++实现的开源项目。使用Apple提供的JavaScriptCore框架,你可以在Objective-C或者基于C的程序中执行Javascript代码,也可以向JavaScript环境中插入一些自定义的对象。JavaScriptCore从iOS 7.0之后可以直接使用。

在JavaScriptCore.h中,我们可以看到这个

#ifndef JavaScriptCore_h#define JavaScriptCore_h#include#include#ifdefined(__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]);Outputvalue=7

三、 JSVirtualMachine

一个JSVirtualMachine的实例就是一个完整独立的JavaScript的执行环境,为JavaScript的执行提供底层资源。

这个类主要用来做两件事情:

实现并发的JavaScript执行

JavaScript和Objective-C桥接对象的内存管理

看下头文件SVirtualMachine.h里有什么:

NS_CLASS_AVAILABLE(10_9,7_0)@interfaceJSVirtualMachine: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)@interfaceJSContext:NSObject/* 创建一个JSContext,同时会创建一个新的JSVirtualMachine */(instancetype)init;/* 在指定虚拟机上创建一个JSContext */(instancetype)initWithVirtualMachine:(JSVirtualMachine*)virtualMachine;/* 执行一段JS代码,返回最后生成的一个值 */(JSValue*)evaluateScript:(NSString*)script;/* 执行一段JS代码,并将sourceURL认作其源码URL(仅作标记用) */-(JSValue*)evaluateScript:(NSString*)script withSourceURL:(NSURL*)sourceURLNS_AVAILABLE(10_10,8_0);/* 获取当前执行的JavaScript代码的context */+(JSContext*)currentContext;/* 获取当前执行的JavaScript function*/+(JSValue*)currentCalleeNS_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*nameNS_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=7a=7a=7

这里列出了三种访问JavaScript对象的方法

通过context的实例方法objectForKeyedSubscript

通过context.globalObject的objectForKeyedSubscript实例方法

通过下标方式

设置属性也是对应的。

API Reference

/* 为JSContext提供下标访问元素的方式 */@interfaceJSContext(SubscriptSupport)/* 首先将key转为JSValue对象,然后使用这个值在JavaScript context的全局对象中查找这个名字的属性并返回 */(JSValue*)objectForKeyedSubscript:(id)key;/* 首先将key转为JSValue对象,然后用这个值在JavaScript context的全局对象中设置这个属性。

可使用这个方法将native中的对象或者方法桥接给JavaScript调用 */(void)setObject:(id)object forKeyedSubscript:(NSObject*)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=100r=230,g=90,b=100r:0g:0b:0

可见,JS中的对象可以直接转换成Objective-C中的NSDictionary,NSDictionary传入JavaScript也可以直接当作对象被使用。

3. NSArray与JS数组

NSArray对象与JavaScript中的array相互转转。其子元素也会递归地进行拷贝和转换。

[context evaluateScript:@“varfriends=['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和不不错,给你再推荐个Jimmycontext[@"girlFriends"]=@[friendsArray[2],@"Jimmy"];[context evaluateScript:@"log('girlFriends :'+girlFriends[0]+' '+girlFriends[1])"];

Output:

Alice,Jenny,XiaoMingAlice,Jenny,XiaoMinggirlFriends: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协议可选择性地导出属性和方法。

后面会详细讲解对象类型的转换。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容