NativeScript是一个runtime,它提供一些机制可使用JavaScript构建原生的IOS、Android甚至WP(将来会加入)应用。NativeScript有不少很是酷的功能,好比MVVM和CSS渲染原生UI。可是NativeScript最使人兴奋的是它使JavaScript能够直接调用native API。javascript
这听起来能够会使人困惑,首先看一个例子,下面是使用NativeScript编写Android app的一段代码:html
var time = new android.text.format.Time(); time.set( 1, 0, 2015 ); console.log( time.format( "%D" ) ); //01/01/15
上述的JavaScript代码实例化了一个Java对象android.text.format.Time
,调用它的set
方法和format
方法而且在控制台输出log。java
咱们先不解释上述代码的实现原理,再看一个使用NativeScript编写IOS app的例子:node
var alert = new UIAlertView(); alert.message = "Hello world!"; alert.addButtonWithTitle( "OK" ); alert.show();
上述的JavaScript代码实例化了一个Objective-C类UIAlertView
,随后给它的message
属性赋值,调用了addButtonWithTitle
方法和show
方法,运行效果以下图:
android
NativeScript并不是只包含JavaScript化的Objective-C和Java代码,还集合了一系列的跨平台module,好比发送http请求、构建UI组件等等。大部分app都须要调用原生的API,NativeScript的runtime简化了原生API的调用方式。ios
这句话能够这么理解,Objective-C和Java也须要调用原生API而且调用方式存在差别,NativeScript削减了差别化,令原生API的调用方式更加简单统一。web
下面咱们看看NativeScript的工做原理。npm
虽然NativeScript的代码看起来很神奇,可是内部的工做原理其实很简单。NativeScript本质上仍然是JavaScript,解析执行JavaScript的天然是JavaScript引擎。在不一样的平台,NativeScript使用平台默认的JavaScript引擎,好比Android平台的V8引擎、IOS平台的JavaScriptCore。既然使用JavaScript引擎解析代码,那么全部的native API的调用语法必须写成规范的JavaScript语法,这样才能够被JavaScript引擎成功解析。编程
NativeScript使用的是最新稳定版本的V8和JavaScriptCore。所以,NativeScript对ECMAScript规范的支持状况与它使用JavaScript的引擎彻底相同。也就是说,Android平台依赖V8对ECMAScript规范的实现程度,IOS依赖JavaScriptCore对ECMAScript规范的实现程度。app
时刻谨记NativeScript是依赖JavaScript引擎这一点很是重要。
咱们再看第一个例子中的第一行代码:
var time = new android.text.format.Time();
在Android平台,上述NativeScript代码由V8及时编译(JIT Compiled)并执行。对于简单的表达式(好比var x = 1 + 2
),咱们很容易理解是怎么工做的。可是V8是如何识别android.text.format.Time
的呢?
V8之因此可以识别android
对象是因为NativeScript runtime把它注入到了JavaScript运行环境中。V8提供了大量的API供使用者配置个性化的JavaScript运行环境,甚至能够注入C++代码用来统计JavaScript的CPU使用状况、管理JavaScript的GC等等。
在这些API当中,有些Context
类能够提供操做全局做用域的API,这就是NativeScript之因此可以在全局做用域内注入android
对象的原理。这种原理其实与Node.js全局方法(好比require()
)的实现原理相同。IOS的JavaScriptCore引擎也提供了相似的机制。
咱们再回顾一下以前的代码:
var time = new android.text.format.Time();
如今咱们知道了这段代码运行在V8上,而且V8能够识别android.text.format.Time()
是由于NativeScript在全局做用域内注入了android
对象。可是仍然有不少疑问没有解决,好比NativeScript如何知道须要注入哪些API?NativeScript如何知道调用Time()
会产生什么效果?
下面咱们依次解决这些疑问。
NativeScript经过reflection(反射)来构建它所运行平台的可用API。不熟悉其余编程语言的JavaScript开发者可能并不了解reflection,JavaScript是一门很是自由的语言,并不须要reflection。可是在其余编程语言中,尤为是Java,reflection是在runtime时获取某个class详细信息的惟一途径。
能够简单的把reflection理解为在runtime(运行时)而不是编译期获取某个object或class完整结构的途径。reflection的详细介绍感兴趣的能够参考这里。
NativeScript使用reflection构建了适用于各平台的API列表。从性能角度来说,生成这些API数据是很是有必要的,NativeScript在编译以前生成这些数据,而后在Android/IOS编译阶段嵌入已生成的元数据。
了解了以上机制以后,咱们再回顾一下以前的代码:
var time = new android.text.format.Time();
如今咱们知道了以上代码之因此可以在V8上运行,使由于NativeScript注入了android.text.format.Time
对象。NativeScript经过一个独立的元数据处理过程当中明确了须要注入的API,而且在Android和IOS的编译阶段嵌入了所需的元数据。
好,咱们继续解答下一个问题:NativeScript是如何将JavaScript的Time()
调用映射到原生的android.text.format.Time()
调用呢?
NativeScript唤起原生代码调用一样依赖于JavaScript引擎的API。上文提到了NativeScript如何对V8引擎注入全局变量,接下来介绍如何经过回调函数实如今JavaScript代码中调用C++代码。
好比在执行new android.text.format.Time()
这段代码,V8引擎将会产生一个回调函数。利用这种机制,NativeScript能够监听JavaScript函数的调用,而且在V8回调函数里执行C++代码,从而实现原生代码的调用。
这里提到的回调函数并非JavaScript的回调函数,而是V8引擎内部的C++函数。V8解析执行JavaScript函数时首先将JavaScript函数映射为C++函数,而后再执行。
Android平台下,NativeScript的C++代码不能直接调用Java的API(好比android.text.format.Time
)。这种状况下须要借助Android平台的JNI(Java Native Interface,Java本地接口)实现C++与Java的桥接。借助于JNI,NativeScript即可以调用Android平台的原生Java API。
IOS平台并不须要相似JNI的桥接机制,由于C++能够直接唤起Objective-C的调用。
了解了以上机制,咱们再回顾一下以前的代码:
var time = new android.text.format.Time();
上文的描述中,咱们知道以上代码能够执行的原理是NativeScript经过单独的元数据生成过程注入了JavaScript引擎android
全局对象。而后在执行Time()
函数时,依次发生了如下行为:
Time()
的行为是实例化native对象android.text.format.Time
;android.text.format.Time
对象而且保持对这个对象的引用;android.text.format.Time
;time
中。这里提到的代理对象是NativeScript用来维持JavaScript对象和native对象的映射关系(mapping)。好比执行如下JavaScript代码:
var time = new android.text.format.Time(); time.set( 1, 0, 2015 );
根据生成的元数据,NativeScript知道代理对象time
的所用API。按照上述步骤,当调用JavaScript函数Time()
时,V8执行对应的回调函数,NativeScript监测到函数的调用,便经过JNI唤起Java的Time
对象的调用。
以上即是NativeScript的工做原理。
至于如何将Objective-C对象和Java对象映射为JavaScript对象,这部分工做很是复杂,由于必须考虑到每种编程语言实现继承模式的差别。感兴趣的能够参考IOS的实现方案和Android的实现方案。
经过以上内容,虽然咱们知道了如何使用JavaScript代码调用原生API,可是若是针对每一个不一样平台都分别编写对应的代码,仍然不可以实现“write once,run anywhere”。为了实现这个目标,NativeScript提供了一种很是强大的功能:NativeScript modules。
NativeScript modules的原理与Node Modules的原理相似,一样遵循CommonJS规范,若是你熟悉Node中require()
和exports
的工做原理,那么NativeScript modules对你来讲便很是容易入手了。
NativeScript modules把各平台专有的API封装成与平台无关的API(相似你们熟知的JavaScript各类兼容性工厂函数)。好比如今咱们须要调用各平台的file API,针对Android平台的代码以下:
new java.io.File( path );
针对IOS平台的代码 以下:
NSFileManager.defaultManager(); fileManager.createFileAtPathContentsAttributes( path );
是否是很麻烦?可是若是使用NativeScript file-system module,你只须要使用统一的API:
var fs = require( "file-system" ); var file = new fs.File( path );
若是你已经掌握了本文提到的NativeScript工做原理,即可以很容易的编写NativeScript Module。好比要编写一个module来获取设备OS的版本:
// device.ios.js module.exports = { version: UIDevice.currentDevice().systemVersion } // device.android.js module.exports = { version: android.os.Build.VERSION.RELEASE }
调用上述Module的方式与调用npm模块相同,使用require()
以下:
var device = require( "./device" ); console.log( device.version );
NativeScript Module下降了web开发者开发native应用的门槛,即便你不熟悉native API,也能够花费很是少的时间阅读各平台的API文档,而后编写一个NativeScript Module来增长后续的开发效率。
本文简单介绍了NativeScript的工做原理,总结以下:
深刻学习资料: