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的工做原理,總結以下:
深刻學習資料: