JavaScriptCore 全面解析 (上篇)

更新时间:2017-04-27 11:06:23 点击次数:1704次

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

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

一、JavaScript

在讨论JavaScriptCore之前,我们首先必须对JavaScript有所了解。

1. JavaScript干啥的?

2. JavaScript起源与历史

3. JavaScript与ECMAScript

4. Java和JavaScript


《雷锋和雷峰塔》

Java 和 JavaScript 是两门不同的编程语言 
一般认为,当时 Netscape 之所以将 LiveScript 命名为 JavaScript,是因为 Java 是当时流行的编程语言,带有 “Java” 的名字有助于这门新生语言的传播。

二、 JavaScriptCore

1. 浏览器演进

2. WebKit排版引擎

webkit是sfari、chrome等浏览器的排版引擎,各部分架构图如下

3. JavaScript引擎

4. JavaScriptCore组成

JavaScriptCore主要由以下模块组成:

关于更多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 <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的主要几个类:

接下来我们会依次讲解这几个类的用法。

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

三、 JSVirtualMachine

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

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

1. 实现并发的JavaScript执行

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

看下头文件SVirtualMachine.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代码,然而,所有其他想要使用该虚拟机的线程都要等待。

通过下面这个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代码

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对象的方法

设置属性也是对应的。

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
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协议可选择性地导出属性和方法。

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

回到顶部
嘿,我来帮您!