前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScriptCore全面解析

JavaScriptCore全面解析

作者头像
用户1097444
发布2022-06-29 17:14:15
1.3K0
发布2022-06-29 17:14:15
举报

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

作者:殷源--腾讯移动端工程师

@IMWeb前端社区

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

5. JavaScriptCore

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

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

这里已经很清晰地列出了JavaScriptCore的主要几个类:

JSContext JSValue JSManagedValue JSVirtualMachine JSExport

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

6. Hello World!

这段代码展示了如何在Objective-C中执行一段JavaScript代码,并且获取返回值并转换成OC数据打印

Output

三、 JSVirtualMachine

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

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

1、实现并发的JavaScript执行

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

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

每一个JavaScript上下文(JSContext对象)都归属于一个虚拟机(JSVirtualMachine)。每个虚拟机可以包含多个不同的上下文,并允许在这些不同的上下文之间传值(JSValue对象)。

然而,每个虚拟机都是完整且独立的,有其独立的堆空间和垃圾回收器(garbage collector ),GC无法处理别的虚拟机堆中的对象,因此你不能把一个虚拟机中创建的值传给另一个虚拟机。

线程和JavaScript的并发执行

JavaScriptCore API都是线程安全的。你可以在任意线程创建JSValue或者执行JS代码,然而,所有其他想要使用该虚拟机的线程都要等待。

如果想并发执行JS,需要使用多个不同的虚拟机来实现。

可以在子线程中执行JS代码。

通过下面这个demo来理解一下这个并发机制

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是:

四、 JSContext

一个JSContext对象代表一个JavaScript执行环境。在native代码中,使用JSContext去执行JS代码,访问JS中定义或者计算的值,并使JavaScript可以访问native的对象、方法、函数。

1. JSContext执行JS代码

调用evaluateScript函数可以执行一段top-level 的JS代码,并可向global对象添加函数和对象定义

其返回值是JavaScript代码中最后一个生成的值

API Reference

2. JSContext访问JS对象

一个JSContext对象对应了一个全局对象(global object)。例如web浏览器中中的JSContext,其全局对象就是window对象。在其他环境中,全局对象也承担了类似的角色,用来区分不同的JavaScript context的作用域。全局变量是全局对象的属性,可以通过JSValue对象或者context下标的方式来访问。

一言不合上代码:

Output:

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

通过context的实例方法objectForKeyedSubscript

通过context.globalObject的objectForKeyedSubscript实例方法

通过下标方式

设置属性也是对应的。

API Reference

五、 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所对应的值也会递归地进行拷贝和转换。

Output:

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

3. NSArray与JS数组

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

Output:

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协议提供了一种声明式的方法去向JavaScript代码导出Objective-C的实例类及其实例方法,类方法和属性。

1. 在JavaScript中调用native代码

两种方式:

1、Block

2、JSExport

Block的方式很简单,如下:

Output:

JSExport的方式需要通过继承JSExport协议的方式来导出指定的方法和属性:

继承于JSExport协议的MyPointExports协议中的实例变量,实例方法和类方法都会被导出,而MyPoint类的- (void)myPrivateMethod方法却不会被导出。 在OC代码中我们这样导出:

在JS代码中可以这样调用:

2. 导出OC方法和属性给JS

默认情况下,一个Objective-C类的方法和属性是不会导出给JavaScript的。你必须选择指定的方法和属性来导出。对于一个class实现的每个协议,如果这个协议继承了JSExport协议,JavaScriptCore就将这个协议的方法和属性列表导出给JavaScript。

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

在Objective-C中通过@property声明的属性决定了JavaScript中的对应属性的特征:

Objective-C类中的属性,成员变量以及返回值都将根据JSValue指定的拷贝协议进行转换。

3. 函数名转换

转换成驼峰形式:

去掉所有的冒号

所有冒号后的第一个小写字母都会被转为大写

4. 自定义导出函数名

如果不喜欢默认的转换规则,也可以使用JSExportAs来自定义转换

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对象:

4. JavaScript属性

ECMAScript定义的对象中有两种属性:数据属性和访问器属性。

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

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

5. JavaScript属性设置与检测

设置一个对象的属性会只会修改或新增其自有属性,不会改变其继承的同名属性

调用一个对象的属性会依次检索本身及其继承的属性,直到检测到

Output:

在chrome的控制台中,我们分别打印设置x属性前后point对象的内部结构:

可见,设置一个对象的属性并不会修改其继承的属性,只会修改或增加其自有属性。

这里我们谈到了proto和继承属性,下面我们详细讲解。

八、 Prototype

JavaScript对于有基于类的语言经验的开发人员来说有点令人困惑 (如Java或C ++) ,因为它是动态的,并且本身不提供类实现。(在ES2015/ES6中引入了class关键字,但是只是语法糖,JavaScript 仍然是基于原型的)。

当谈到继承时,Javascript 只有一种结构:对象。每个对象都有一个内部链接到另一个对象,称为它的原型 prototype。该原型对象有自己的原型,等等,直到达到一个以null为原型的对象。根据定义,null没有原型,并且作为这个原型链 prototype chain中的最终链接。

任何一个对象都有一个__proto__属性,用来表示其继承了什么原型。

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

在Chrome的控制台中,我们打印对象结构:

可见继承关系,point继承的原型又继承了Object.prototype,而Object.prototype的__proto__指向null,因而它是继承关系的终点。 这里我们首先要知道prototype和__proto__是两种属性,前者只有function才有,后者所有的对象都有。后面会详细讲到。

1. JavaScript类?

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

在JavaScript中我们可以通过function来模拟类,例如我们定义一个MyPoint的函数,并把他认作MyPoint类,就可以通过new来创建具有x,y属性的对象

打印point对象结构:

这里出现一个constructor的概念

2. JavaScript constructor

每个JavaScript函数都自动拥有一个prototype的属性,这个prototype属性是一个对象,这个对象包含唯一一个不可枚举属性constructor。constructor属性值是一个函数对象

执行以下代码我们会发现对于任意函数 F.prototype.constructor == F

这里即存在一个反向引用的关系:

3. new发生了什么?

当调用new MyPoint(99, 66)时,虚拟机生成了一个point对象,并调用了MyPoint的prototype的constructor对象对point进行初始化,并且自动将MyPoint.prototype作为新对象point的原型。 相当于下面的伪代码

4. __proto__与prototype

简单地说:

__proto__是所有对象的属性,表示对象自己继承了什么对象

prototype是Function的属性,决定了new出来的新对象的__proto__

如图详细解释了两者的区别

5. 打印JavaScript对象结构

在浏览器提供的JavaScript调试工具中,我们可以很方便地打印出JavaScript对象的内部结构

在Mac/iOS客户端JavaScriptCore中并没有这样的打印函数,这里我自定义了一个打印函数

鉴于对象的内部结构容易出现循环引用导致迭代打印陷入死循环,我们在这里简单地处理,对属性不进行迭代打印。

为了描述对象的原型链,这里手动在对象末尾对其原型进行打印。

6. log

我们为所有的context都添加一个log函数,方便我们在JS中向控制台输出日志

九、 导出OC对象给JS

现在我们继续回到Objective-C中,看下OC对象是如何导出的

1. 简单对象的导出

当你从一个未指定拷贝协议的Objective-C实例创建一个JavaScript对象时,JavaScriptCore会创建一个JavaScript的wrapper对象。对于具体类型,JavaScriptCore会自动拷贝值到合适的JavaScript类型。

以下代码定义了一个继承自NSObject的简单类

导出对象

然后我们打印JavaScript中的d_point对象结构如下:

可见,其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

在OC中,它的继承关系是这样的

在JS中,它的继承关系是这样的

打印对象结构来验证:

Output:

可见,DPoint自身的未导出的属性type没有在JS对象中反应出来,其继承的MyPoint的导出的属性和函数都在JS对象的原型中。

十、 内存管理

1. 循环引用

之前已经讲到, 每个JSValue对象都持有其JSContext对象的强引用,只要有任何一个与特定JSContext关联的JSValue被持有(retain),这个JSContext就会一直存活。如果我们将一个native对象导出给JavaScript,即将这个对象交由JavaScript的全局对象持有,引用关系是这样的:

这时如果我们在native对象中强引用持有JSContext或者JSValue,便会造成循环引用:

因此在使用时要注意以下几点:

2. 避免直接使用外部context

避免在导出的block/native函数中直接使用JSContext

使用 [JSContext currentContext] 来获取当前context能够避免循环引用

3. 避免直接使用外部JSValue

避免在导出的block/native函数中直接使用JSValue

这里我们使用了JSManagedValue来解决这个问题

十一、 JSManagedValue

一个JSManagedValue对象包含了一个JSValue对象,“有条件地持有(conditional retain)”的特性使其可以自动管理内存。

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

不要在在一个导出到JavaScript的native对象中持有JSValue对象。因为每个JSValue对象都包含了一个JSContext对象,这种关系将会导致循环引用,因而可能造成内存泄漏。

1. 有条件地持有

所谓“有条件地持有(conditional retain)”,是指在以下两种情况任何一个满足的情况下保证其管理的JSValue被持有:可以通过JavaScript的对象图找到该JSValue

可以通过native对象图找到该JSManagedValue。使用addManagedReference:withOwner:方法可向虚拟机记录该关系反之,如果以上条件都不满足,JSManagedValue对象就会将其value置为nil并释放该JSValue。

JSManagedValue对其包含的JSValue的持有关系与ARC下的虚引用(weak reference)类似。

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

通常我们使用weak来修饰block内需要使用的外部引用以避免循环引用,由于JSValue对应的JS对象内存由虚拟机进行管理并负责回收,这种方法不能准确地控制block内的引用JSValue的生命周期,可能在block内需要使用JSValue的时候,其已经被虚拟机回收。

API Reference

十二、 异常处理

JSContext的exceptionHandler属性可用来接收JavaScript中抛出的异常

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

因此,默认的表现就是从JavaScript中抛给native的未处理的异常又被抛回到JavaScript中,异常并未被捕获处理。

将context.exception设置为nil将会导致JavaScript认为异常已经被捕获处理。

扫码下方二维码,

随时关注更多前端干货文章!

微信:IMWebTech

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-04-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯IMWeb前端团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、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
                        • 线程和JavaScript的并发执行
                        • 四、 JSContext
                          • 1. JSContext执行JS代码
                            • 2. JSContext访问JS对象
                            • 五、 JSValue
                              • 1. JSValue类型转换
                                • 2. NSDictionary与JS对象
                                  • 3. NSArray与JS数组
                                    • 4. Block/函数和JS function
                                      • 5. OC对象和JS对象
                                      • 六、 JSExport
                                        • 1. 在JavaScript中调用native代码
                                          • 2. 导出OC方法和属性给JS
                                            • 4. 自定义导出函数名
                                              • 5. 导出OC对象给JS
                                              • 七、 JavaScript对象继承
                                                • 1. JavaScript的数据类型
                                                  • 2. JavaScript原始值
                                                    • 3. JavaScript对象
                                                      • 4. JavaScript属性
                                                        • 5. JavaScript属性设置与检测
                                                        • 八、 Prototype
                                                          • 1. JavaScript类?
                                                            • 2. JavaScript constructor
                                                              • 3. new发生了什么?
                                                                • 4. __proto__与prototype
                                                                  • 5. 打印JavaScript对象结构
                                                                    • 6. log
                                                                    • 九、 导出OC对象给JS
                                                                      • 1. 简单对象的导出
                                                                        • 2. 继承关系的导出
                                                                        • 十、 内存管理
                                                                          • 1. 循环引用
                                                                            • 2. 避免直接使用外部context
                                                                              • 3. 避免直接使用外部JSValue
                                                                              • 十一、 JSManagedValue
                                                                                • 1. 有条件地持有
                                                                                  • 2. 为什么不直接用虚引用?
                                                                                  • 十二、 异常处理
                                                                                  领券
                                                                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档