ECMAScriptJavaScriptCore周到解析 (下篇)

迎接大家关注腾讯云技术社区-乐乎官方主页,大家将持续在乐乎为大家推荐技术精品小说哦~

殷源,专注移动客户端支出,微软Imagine
Cup中中原人民共和国区特等奖获得者,现就职于腾讯。

JavaScriptCore全面解析
(上篇)

六、 JSExport

JSExport磋商提供了一种注解式的主意去向JavaScript代码导出Objective-C的实例类及其实例方法,类措施和总体性。

1. 在JavaScript中调用native代码

二种艺术:

  • Block

  • JSExport

Block的方法不会细小略,如下:

context[@"add"] = ^(NSInteger a, NSInteger b) {
    return a+b;
};

JSValue *resultValue = [context evaluateScript:@"add(5, 6)"];
//另外一种调用JS函数的方法
resultValue = [context[@"add"] callWithArguments:@[@(5), @(6)]];
NSLog(@"resultValue = %@", resultValue);

Output:

11

JSExport的办法亟待经过持续JSExport磋商的点子来导出钦命的主意和属性:

@class MyPoint;

@protocol MyPointExports <JSExport>
@property double x;
@property double y;
- (NSString *)description;
- (instancetype)initWithX:(double)x y:(double)y;
+ (MyPoint *)makePointWithX:(double)x y:(double)y;
@end

@interface MyPoint : NSObject <MyPointExports>
- (void)myPrivateMethod; // Not in the MyPointExports protocol, so
    not visible to JavaScript code.
+ (void)test;
@endltValue);

接轨于JSExport协议的MyPointExports协议中的实例变量,实例方法和类措施都会被导出,而MyPoint类的-
(void)myPrivateMethod方法却不会被导出。

在OC代码中大家如此导出:

//导出对象
context[@"point"] = [[MyPoint alloc] initWithX:6 y:8];

//导出类
context[@"MyPoint"] = [MyPoint class];

在JS代码中能够这么调用:

// Objective-C properties become fields. 
point.x; 

point.x = 10; 

// Objective-C instance methods become functions. 
point.description(); 

// Objective-C initializers can be called with constructor syntax. 
var p = MyPoint(1, 2); 

// Objective-C class methods become functions on the constructor object. 
var q = MyPoint.makePointWithXY(0, 0);

2. 导出OC方法和总体性给JS

  • 私下认可情况下,1个Objective-C类的格局和总体性是不会导出给JavaScript的。你必须挑选内定的方法和性质来导出。对于1个class达成的每一种体协会议,纵然那么些协议持续了JSExport商谈,JavaScriptCore就将以此协议的点子和属性列表导出给JavaScript。

  • 对于每一个导出的实例方法,JavaScriptCore都会在prototype中创建叁个存取器属性。对于每二个导出的类措施,JavaScriptCore会在constructor对象中创设1个应和的JavaScript
    function。

  • 在Objective-C中通过@property评释的质量决定了JavaScript中的对应属性的风味:

ECMAScript 1

  • Objective-C类中的属性,成员变量以及再次来到值都将依据JSValue钦定的正片协议实行转移。

3. 函数名转移

转换来驼峰情势:

  • 铲除具有的冒号

  • 拥有冒号后的首先个小写字母都会被转为大写

ECMAScript 2

4. 自定义导出函数名

倘使不欣赏暗中认可的转换规则,也足以动用JSExportAs来自定义转换

ECMAScript 3

5. 导出OC对象给JS

  • 怎么着导出自定义的靶子?

  • 自定义对象有千头万绪的继承关系是什么导出的?

在座谈这些话题以前,大家先是需求对JavaScript中的对象与继承关系具有了然。

七 、 JavaScript对象继承

只要您曾经理解JavaScript的靶子继承,能够跳过本节。

此处会火速介绍JavaScript对象继承的部分文化:

1. JavaScript的数据类型

新颖的 ECMAScript 标准定义了 7 种数据类型:

6 种 原始类型:

  • Boolean

  • Null

  • Undefined

  • Number

  • String

  • Symbol (ECMAScript 6 新定义)和 Object

2. JavaScript原始值

除 Object
以外的保有品类都以不可变的(值小编无法被转移)。我们称那几个品种的值为“原始值”。

  • 布尔类型:四个值:true 和 false

  • Null 类型:唯有二个值: null

  • Undefined 类型:贰个尚未被赋值的变量会有个默许值 undefined

  • 数字类型

  • 字符串类型:分化于类 C 语言,JavaScript
    字符串是不足更改的。那象征字符串一旦被创制,就不可能被改动

  • 标志类型

3. JavaScript对象

在 Javascript
里,对象足以被当作是一组属性的集合。那一个属性还可以被增减。属性的值能够是随便档次,包蕴拥有复杂数据结构的指标。

以下代码构造了二个point对象:

var point = { 
    x : 99, 
    y : 66, 
    revers : function() { 
        var tmp = this.x 
        this.x = this.y 
        this.y = tmp 
    }, 
    name : 'BiuBiuBiu', 
    next : null 
} 

point.revers();

4. JavaScript属性

ECMAScript定义的目的中有二种属性:数据属性和做客器属性。

  • 多少属性

数量属性是键值对,并且每种数据属性拥有下列特征:

ECMAScript 4

  • 走访器属性

访问器属性有叁个或八个访问器函数 (get 和 set)
来存取数值,并且有以下特点:

ECMAScript 5

5. JavaScript属性设置与检测

  • 安装3个指标的属性会只会修改或新增其自有质量,不会改变其后续的同名属性

  • 调用二个对象的属性会挨个检索自身及其继承的品质,直到检查和测试到

var point = {x:99, y:66}; 
var childPoint = Object.create(point); 
console.log(childPoint.x) 
childPoint.x = 88 
console.log(childPoint.x)

Output:

99
88

在chrome的控制长沙,我们独家打字与印刷设置x属性前后point对象的内部结构:

设置前

ECMAScript 6

设置后

ECMAScript 7!

可知,设置3个对象的属性并不会修改其后续的品质,只会修改或扩大其自有质量。

此间大家谈到了proto和三番五次属性,下边我们详细讲解。

八、 Prototype

JavaScript对于有根据类的言语经验的开发职员来说多少令人质疑 (如Java或C
++)
,因为它是动态的,并且笔者不提供类完毕。(在ES2014/ES6中引入了class关键字,不过只是语法糖,JavaScript
依然是依据原型的)。

当谈到一连时,Javascript
唯有一种结构:对象。各类对象都有3个里边链接到另八个对象,称为它的原型
prototype。该原型对象有自个儿的原型,等等,直到达到叁个以null为原型的靶子。依照定义,null没有原型,并且作为那一个原型链
prototype chain中的最后链接。

其余贰个目标都有1个proto属性,用来表示其继承了什么样原型。

以下代码定多个具有继续关系的指标,point对象继承了三个具有x,y属性的原型对象。

var point = { 
    name : null, 
    __proto__ : { 
        x:99, 
        y:66, 
        __proto:Object.prototype 
    } 
}

Object.prototype.__proto__ == null        \\true

在Chrome的控制巴尔的摩,大家打字与印刷对象组织:

ECMAScript 8

看得出继承关系,point继承的原型又持续了Object.prototype,而Object.prototype的proto指向null,由此它是连续关系的终极。
此处大家先是要了然prototype和proto是两种天性,前者只有function才有,后者有着的目标都有。前面会详细讲到。

1. JavaScript类?

Javascript 唯有一种结构:对象。类的概念又从何而来?

在JavaScript中大家得以经过function来模拟类,例如大家定义多个MyPoint的函数,并把她认作MyPoint类,就足以因此new来创设具有x,y属性的对象

function MyPoint(x, y) { 
    this.x = x; 
    this.y = y; 
} 

var point = new MyPoint(99, 66);

打字与印刷point对象组织:

ECMAScript 9

那边出现一个constructor的定义

2. JavaScript constructor

各个JavaScript函数都自动拥有3个prototype的性质,这几个prototype属性是3个目标,那个指标涵盖唯一3个不计其数属性constructor。constructor属性值是多少个函数对象

实践以下代码大家会意识对于任意函数F.prototype.constructor == F

var F = function(){}; //一个函数对象F 

var p = F.prototype; //F关联的原型对象 

var c = p.constructor; //原型对象关联的constructor函数

c == F // =>true: 对于任意函数F.prototype.constructor == F

此处即存在二个反向引用的涉嫌:

ECMAScript 10

3. new产生了怎么?

当调用new MyPoint(99,
66)时,虚拟机生成了四个point对象,并调用了MyPoint的prototype的constructor对象对point举行发轫化,并且自动将MyPoint.prototype作为新对象point的原型。
一定于下边包车型客车伪代码

var point ;
point = MyPoint.prototype.constructor(99,66);
point.__proto__ = MyPoint.prototype;

ECMAScript 11

4. _ proto __ 与prototype

简易地说:

  • _proto__是独具指标的习性,表示对象本人再三再四了怎样指标

  • prototype是Function的脾性,决定了new出来的新目的的proto

如图详细分解了双边的界别

ECMAScript 12!

5. 打字与印刷JavaScript对象协会

  • 在浏览器提供的JavaScript调试工具中,大家得以很便宜地打字与印刷出JavaScript对象的内部结构

  • 在Mac/iOS客户端JavaScriptCore中并不曾这么的打字与印刷函数,那里自身自定义了叁个打印函数。鉴于对象的内部结构容易并发循环引用导致迭代打字与印刷陷入死循环,大家在此处大约地拍卖,对品质不实行迭代打字与印刷。为了描述对象的原型链,那里手动在对象末尾对其原型举办打字与印刷。

function __typeof__(objClass)
{
    if ( objClass && objClass.constructor )
    {
        var strFun = objClass.constructor.toString();
        var className = strFun.substr(0, strFun.indexOf('('));
        className = className.replace('function', '');
        return className.replace(/(^\s*)|(\s*$)/ig, '');
    }
    return typeof(objClass);
}

function dumpObj(obj, depth) {

    if (depth == null || depth == undefined) {
        depth = 1;
    }
    if (typeof obj != "function" && typeof obj != "object") {
        return '('+__typeof__(obj)+')' + obj.toString();
    }

    var tab = '    ';
    var tabs = '';
    for (var i = 0; i<depth-1; i++) {
        tabs+=tab;
    }

    var output = '('+__typeof__(obj)+') {\n';

    var names = Object.getOwnPropertyNames(obj);
    for (index in names) {
        var propertyName = names[index];

        try {
            var property = obj[propertyName];
            output += (tabs+tab+propertyName + ' = ' + '('+__typeof__(property)+')' +property.toString()+ '\n');
        }catch(err) {
            output += (tabs+tab+propertyName + ' = ' + '('+__typeof__(property)+')' + '\n');
        }
    }

    var prt = obj.__proto__;
    if (typeof obj == "function") {
        prt = obj.prototype;
    }

    if (prt!=null && prt!= undefined) {
        output += (tabs+tab+'proto = ' + dumpObj(prt, depth+1) + '\n');
    }else {
        output += (tabs+tab+'proto = '+prt+' \n');
    }

    output+=(tabs+'}');
    return output;
}

function printObj(obj) {
    log(dumpObj(obj));
}

6. log

我们为富有的context都添加二个log函数,方便我们在JS中向决定台出口日志

context[@"log"] = ^(NSString *log) {
        NSLog(@"%@", log);
};

九、 导出OC对象给JS

今天大家后续回来Objective-C中,看下OC对象是哪些导出的

1. 简短对象的导出

当您从2个未内定拷贝协议的Objective-C实例创设三个JavaScript对象时,JavaScriptCore会创立1个JavaScript的wrapper对象。对于具体品种,JavaScriptCore会自动拷贝值到适合的JavaScript类型。

以下代码定义了贰个继续自NSObject的大致类

@interface DPoint : NSObject

@property (nonatomic, retain) NSString *type;

@end

导出对象

DPoint *dPoint = [[DPoint alloc] init];
dPoint.type = @"Hello Point!";
//导出对象
context[@"d_point"] = dPoint;
[context evaluateScript:@"printObj(d_point)"];

然后大家打字与印刷JavaScript中的d_point对象协会如下:

//Output
() { 
    proto = () { 
        constructor = (Object)[object DPointConstructor] 
        proto = (Object) { 
            toString = (Function)function toString() { [native code] } 
            toLocaleString = (Function)function toLocaleString() { [native code] } 
            valueOf = (Function)function valueOf() { [native code] } 
            hasOwnProperty = (Function)function hasOwnProperty() { [native code] } 
            propertyIsEnumerable = (Function)function propertyIsEnumerable() { [native code] } 
            isPrototypeOf = (Function)function isPrototypeOf() { [native code] } 
            __defineGetter__ = (Function)function __defineGetter__() { [native code] } 
            __defineSetter__ = (Function)function __defineSetter__() { [native code] } 
            __lookupGetter__ = (Function)function __lookupGetter__() { [native code] } 
            __lookupSetter__ = (Function)function __lookupSetter__() { [native code] } 
            __proto__ = (object) 
            constructor = (Function)function Object() { [native code] } 
            proto = null 
        } 
    } 
}

足见,其type属性并没有被导出。

JS中的对象原型是便是Object.prototype。

2. 继承关系的导出

在JavaScript中,继承关系是通过原型链(prototype
chain)来援救的。对于每个导出的Objective-C类,JavaScriptCore会在context中开创三个prototype。对于NSObject类,其prototype对象就是JavaScript
context的Object.prototype。

对此有着别的的Objective-C类,JavaScriptCore会创造多少个prototype属性指向其父类的原型属性的原型对象。如此,JavaScript中的wrapper对象的原型链就反映了Objective-C中项目标接轨关系。

我们让DPoint继承子MyPoint

@interface DPoint : MyPoint

@property (nonatomic, retain) NSString *type;

@end

在OC中,它的继承关系是那般的

ECMAScript 13

在JS中,它的继续关系是那样的

ECMAScript 14

打字与印刷对象组织来注解:

//导出类
context[@“DPoint"] = [DPoint class] ;
[context evaluateScript:@“log(Dpoint.prototype.constructor==DPoint)"];
[context evaluateScript:@"printObj(DPoint)"];

Output:

true
(Function) { 
    name = (String)DPoint 
    prototype = (DPoint)[object DPointPrototype] 
    proto = (DPoint) { 
        constructor = (Function)function DPoint() { [native code] } 
        proto = (MyPoint) { 
            constructor = (Function)function MyPoint() { [native code] } 
            description = (Function)function () { [native code] } 
            x = (Function) 
            y = (Function) 
            proto = (Object) {
        toString = (Function)function toString() { [native code] } 
        toLocaleString = (Function)function toLocaleString() { [native code] } 
        ……        
        __proto__ = (object) 
        constructor = (Function)function Object() { [native code] } 
        proto = null 
    } 
        } 
    } 
}

足见,DPoint自己的未导出的品质type没有在JS对象中反响出来,其继续的MyPoint的导出的习性和函数都在JS对象的原型中。

⑩ 、 内部存款和储蓄器管理

1. 循环引用

前边曾经讲到,
每一个JSValue对象都持有其JSContext对象的强引用,只要有此外几个与特定JSContext关联的JSValue被抱有(retain),那一个JSContext就会直接存活。倘若大家将一个native对象导出给JavaScript,即将那一个目的交由JavaScript的大局对象具备
,引用关系是如此的:

ECMAScript 15

此时即使大家在native对象中强引用持有JSContext也许JSValue,便会导致循环引用:

ECMAScript 16

所以在行使时要小心以下几点:

2. 幸免直接利用外部context

  • 防止在导出的block/native函数中央直机关接使用JSContext

  • 使用 [JSContext currentContext] 来获取当前context可防止止循环引用

//错误用法
context[@"block"] = ^() {
    NSLog(@"%@", context);
};

//纠正用法
context[@"block"] = ^() {
    NSLog(@"%@", [JSContext currentContext]);
};

3. 幸免直接选拔外部JSValue

  • 幸免在导出的block/native函数中一向利用JSValue

//错误用法
JSValue *value = [JSValue valueWithObject:@"test“ inContext:context];
context[@"block"] = ^(){
    NSLog(@"%@", value);
};

//纠正用法
JSValue *value = [JSValue valueWithObject:@"test“ inContext:context];
JSManagedValue *managedValue = [JSManagedValue managedValueWithValue:value     andOwner:self];
context[@"block"] = ^(){
    NSLog(@"%@", [managedValue value]);
};

此地我们选拔了JSManagedValue来消除这些题材

十一、 JSManagedValue

  • 三个JSManagedValue对象涵盖了3个JSValue对象,“有标准地具有(conditional
    retain)”的特点使其能够自行政管理理内部存款和储蓄器。

  • 最基本的用法便是用来在导入到JavaScript的native对象中储存JSValue。

  • 毫无在在三个导出到JavaScript的native对象中有着JSValue对象。因为各种JSValue对象都包涵了一个JSContext对象,这种关涉将会促成循环引用,由此恐怕导致内部存款和储蓄器泄漏。

1. 有规范地享有

所谓“有规范地享有(conditional
retain)”,是指在以下三种状态其余3个满意的景况下保障其管理的JSValue被有着:能够透过JavaScript的目的图找到该JSValue

  • 能够透过native对象图找到该JSManagedValue。使用addManagedReference:withOwner:方法可向虚拟机记录该关系反之,借使以上标准都不知足,JSManagedValue对象就会将其value置为nil并释放该JSValue。

  • JSManagedValue对其包涵的JSValue的具有关系与ASportageC下的虚引用(weak
    reference)类似。

2. 为什么不直接用虚引用?

常备大家使用weak来修饰block内亟待利用的外表引用防止止循环引用,由于JSValue对应的JS对象内部存款和储蓄器由虚拟机进行田管并肩负回收,那种情势无法纯粹地控制block内的引用JSValue的生命周期,恐怕在block内亟待选择JSValue的时候,其早已被虚拟机回收。

API Reference

/* 可以直接使用JSManagedValue的类方法直接生产一个带owner的对象 */
+ managedValueWithValue:andOwner:

/* 也可以使用JSVirtualMachine的实例方法来手动管理 */
addManagedReference:withOwner: 
removeManagedReference:withOwner:


/* owner即JSValue在native代码中依托的对象,虚拟机就是通过owner来确认native中的对象图关系 */

十贰 、 至极处理

  • JSContext的exceptionHandler属性可用来接收JavaScript中抛出的不行

  • 默认的exceptionHandler会将exception设置给context的exception属性

  • 就此,暗中同意的显现正是从JavaScript中抛给native的未处理的尤其又被抛回到JavaScript中,至极没有被抓走处理。

  • 将context.exception设置为nil将会导致JavaScript认为不行已经被抓获处理。

@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);

context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
    NSLog(@"exception : %@", exception);
    context.exception = exception;
};

参考:

https://trac.webkit.org/wiki/JavaScriptCore

https://trac.webkit.org/browser/trunk/Source/JavaScriptCore

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

https://developer.apple.com/reference/javascriptcore

http://blog.iderzheng.com/introduction-to-ios7-javascriptcore-framework/

http://blog.iderzheng.com/ios7-objects-management-in-javascriptcore-framework/

 

连锁推荐

玩转JavaScript正则表明式
前端 fetch
通信

营造流式应用—奔驰G级xJS详解


 

此文已由小编授权腾讯云技术社区颁发,转发请评释文章出处
初稿链接:https://www.qcloud.com/community/article/516026
收获越多腾讯海量技术实施干货,欢迎我们前往腾讯云技术社区