介绍
GIC
从0.3.0版本开始正式支持JavaScript
,也就意味你可以直接使用JavaScript
来写业务逻辑,至此开始,结合XML
、js文件
、图片资源
等静态文件,完全可以将整个的APP做成一个可以热更新的应用。另外,在开发的时候也可以通过HotReload
的方式,无需编译整个APP就能实时刷新应用,进一步的加快应用的开发效率。
在最新的0.4.8
版本中,GIC
已经支持大部分的ES6特性,包括但不限于yield
、generator
、Promise
等等,并且也支持了ES8
中的async
、await
GIC
在最初的架构中根本就没有考虑对JavaScript
提供支持,数据绑定
、事件绑定
等等功能统统都是为native code
设计的。而后来当我考虑想要对JavaScript
进行支持的时候,也仅仅是通过behavior
来实现。事实上,如果你看过GIC
的源码,你会发现GIC
可以实现对任意脚本的支持,而实现的方式也仅仅只是通过自定义behavior
来实现。从这一点来说,侧面反映了behavior
功能的强大之处。
GIC
对于JavaScript
的支持是基于JavaScriptCore
这个苹果官方framework
实现的,其实这个framework
本来就是开源的,而且还是属于大名鼎鼎的其中的模块。再加上Webkit
的跨平台能力,也就意味着只要是基于JavaScriptCore
开发的功能,同样可以移植到安卓上面,也就是意味着GIC
在对JavaScript
的支持上具备了跨平台的潜力。
对ES6的支持
首先各位要知道的是,iOS对于Es6
规范的支持程度在不同的iOS版本中是不同的,iOS8
对于ES6
是完全不支持的,iOS9
部分支持,最新的iOS12
基本已经完全支持了ES6
的规范。当然,这里就不列出不同的iOS版本具体支持哪些ES6特性,你只需要知道,不同的iOS版本对于不同的ES6规范支持程度不一就行了。
然而我们的app肯定是要运行在低版本上的,那么如何解决这个问题?
事实上,GIC
本身并没有提供原生的解决方案,虽然也尝试过内置babel
来实现实时转码,但是后来发现性能太差,对于复杂的JS文件进行转码会耗费大量的CPU资源。因此就断了内置babel
的想法。
但是GIC
通过为VSCode
开发插件的方法,曲线实现了对ES6
的完整支持,VSCode的插件(GICVSCodeExtension
)可以一次性将整个工程的JS代码编译成ES5
的代码。也就意味你可以放心的在你的工程中编写ES6
的代码,然后通过VSCode进行编译,进而使得你的JS代码可以运行在不同的iOS版本上。
事实上,VSCode插件也是通过babel来转码的,并且GIC也自定义了babel插件。
你只需要通过GIC
提供的脚手架来创建项目就能获得这样的能力,详细的脚手架以及IDE支持的介绍可以查看。
在最新的0.4.8
版本中,新增了对yield
、generator
、Promise
、async
、await
的支持。
require
在GIC
中,打开一个新页面相当于在浏览器中打开一个新页面,页面跟页面之间并不能共享JavaScript
执行环境(JSContext
),每个页面都有一个独立的sand box
(JSContext
)。因此,当一个页面需要某个功能的时候你需要通过require
方法手动的将这个功能引入,每个页面都是如此,同一个功能如果在不同的页面中使用,那么意味着你需要在不同的页面单独引入。
当前require
的功能更多的类似Nodejs
的用法,如果你以前接触过nodejs的话,那么很容易理解。
其实,GIC
提供的require
函数相较于Nodejs
中的require
函数来说只是一个简化版的实现,功能比较简单。
使用module.exports
来导出,使用require
函数来导入,当前require
函数支持js
和json
文件的导入。
比如: 在a.js
中定义
class ClassA {}module.exports = ClassA;复制代码
在b.js
中引用
const ClassA = require('/js/a.js');复制代码
或者一次性导出多个,比如: 在a.js
中定义
class ClassA {}class ClassB extends ClassA {}class ClassC {}module.exports = { ClassA, ClassB, ClassC };复制代码
在b.js
中引用
const { ClassA, ClassB, ClassC } = require('/js/a.js');复制代码
以上用法,写过node.js的都很容易明白。唯一的区别就是,GIC
中的路径全部是绝对路径。但是如果您是使用VSCode开发的话,那么可以借助插件自动将相对路径编译成绝对路劲。
起初设计require
函数实现的时候并没有参考nodejs
,但是后来发现功能实在太弱,无法实现模块化,然后又重新设计了require
的实现,但是神奇的是,等我写完以后才发现,这他喵的不就是nodejs中的require
嘛!如果你用VSCode开发,那么你会发现,当你使用require
导入JS文件后,VSCode竟然也能准确的识别,并且提供了完整的智能提示
功能。
引入第三方库
GIC
不支持npm包管理工具
,如果你想在工程中使用第三方库的话只能直接将JS文件引入的方式来使用。拿axios
举例
- 下载axios的JS文件。
地址如下:
- 将下载的JS文件拷贝到工程的js文件夹下面。
- 使用require函数以非module模式引入axios,
require('./axios.js', false);复制代码
- 直接在代码中使用
axios.get('https://www.sojson.com/open/api/lunar/json.shtml') .then((response) => { console.log(JSON.stringify(response)); }) .catch(function (err) { console.log(err); });复制代码
其他的第三方库都可以采用这样的方式引入。但是需要注意的是,第三方库可能用到了GIC
本身不支持的API,这时候就需要你自己以JSAPI扩展
的方式来提供支持了。
调试
很遗憾,当前由于JavaScriptCore
的限制,GIC
并不具备JS
调试的能力。目前调试手段只能是通过console.log来追踪,更进一步的是通过直接打印调用堆栈的方式来实现方法调用追踪,但是调用堆栈的信息在JSContext
中并不详细。
另外,你也可以通过safari
来查看某个JSContext
对象,来查看JSContext
的内容。
目前的调试手段有限,而作者也在研究如何配合VSCode实现调试功能,不过目前进展不大。如果有哪位大能之士有方案的话还请告知下。
JSAPI扩展。
上面已经提过,GIC
对JavaScript
的支持,是通过JavaScriptCore
来实现的。具体一点是通过JSContext
、JSValue
来实现的。然而JavaScriptCore
本身提供的API是有限的,比如console
、XMLHttpRequest
、setTimeout
等API是没有的,只能通过扩展来实现。GIC
已经提供了一些JavaScriptCore
所没有的API,而其他的API就需要你自己来实现了。
然而对于目前很多从npm安装的库来说,很遗憾,GIC
不支持。但是你可以使用直接加载js文件的方式来引入您的工程,但是注意这些库有可能用到了其他的一些api,那么这时候就需要你自己扩展实现这些API以便提供支持。
其实对于第三方库的支持已经跟GIC
本身没有关系了,完全可以通过扩展+后期编译
的方式来实现支持。不过这样的工程会比较大,你不可能做到对所有库的支持,只能针对特定库做有限的支持。
JSValue注意事项。
在实际的项目开发过程中,免不了要自定义JSAPI的扩展,而实现JSAPI的扩展那么你就回避不了对JSValue
的使用,但是JSValue
还是有些地方需要注意的,否则会为你的代码埋下内存管理的隐患。
各位在看这篇博客之前如果没有接触过JavaScriptCore
相关的内容,我还是建议各位先去了解下JavaScriptCore
,尤其是里面的JSContext
和JSValue
。
JSValue
的最大问题就在于内存管理的问题。
JS和OC在内存管理方面是不一样的,JS的内存管理机制被叫做垃圾回收
,而OC的内存管理是基于引用计数
的,因此,这两种语言如果想要实现互调,那么必须得解决内存管理的问题。而JSValue
就是干这个事的,但是千万不要以为只要使用了JSValue
就万事无忧了。
JSValue
是OC的对象,需要遵循引用计数
的内存管理机制,而JSValue
指向的JS对象
却是垃圾回收的,如果你在代码中直接将JSValue
作为变量保存下来那么等待你的有可能就是循环引用。这时候为了解决循环引用的问题,就需要JSManagedValue
出场了,JSManagedValue
专门为了解决这个问题的,即能让你拥有JS对象,也不会引起循环引用的问题,可以理解为OC对JSValue
的弱引用。
如果出现了循环引用,系统有可能报Attempting to release access but the mutator does not have access
这样的错误,这时候App会直接崩溃。那么这时候就需要检查你的代码中是否循环引用了JSValue
RootDataContext
如果要在XML中直接写JS代码,这里几个概念需要注意的。具体
RootDataContext
--根数据源,也即是你在一个页面中第一个设置的数据源。比如:
复制代码
上面的JS代码$el.dataContext = new Home();
就是为page
元素设置数据源,并且这时候,RootDataContext
就指向Home的实例
。
在实际的开发过程中你不会直接接触RootDataContext
,而是通过事件
、绑定
等形式间接的接触。你可以在数据绑定的表达式
、事件表达式
中通过this指针
来访问。比如:
复制代码
这样,在tap
事件中,绑定了一个JS回调,this.onClicked()
指向classHome
的onClicked
方法。
而有的时候你可能需要直接在事件回调中修改元素的属性,那么可以通过$el
来访问该元素本身,然后设置属性。比如:
复制代码
这样,当该lable
被点击的时候,文本内容就会被修改成i clicked
。
事实上,上面在为
page
设置数据源的时候,就是通过$el
来设置的。
yield、generator
GIC
在最新的版本中已经提供了对yield
、generator
的支持,而且generator
是由native code
开发的,并不是babel
转码支持的,babel
转码只能提供对yield
的支持,但是generator
转码过后就无法在iOS上运行了,因此我参考了generator
的API,以JSAPI扩展
的方式,用objective-c
实现了generator
整套API。实现过程我后面准备单写一篇博客来介绍如何实现。
GIC
也已经提供了对Promise
的支持,因此在Promise
和yield
的基础上,提供对async
、await
支持就变得顺理成章了。事实上,GIC
已经支持ES8
中的async
、await
功能了。
数据绑定
GIC
本身是支持数据绑定的,在引入JavaScript
后也提供了数据绑定的功能。但是JS的数据绑定跟naitve coed
的数据绑定在实现方式上是不一样的,GIC
在实现JS的数据绑定过程中,充分参考了VUE
中的实现方式,并且研究了VUE
的源码,最终将VUE
的实现方式移植到了GIC
中。可以