介紹
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其中的模組。再加上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
–根資料來源,也即是你在一個頁面中第一個設定的資料來源。比如:
<
page title="Home">
<
behaviors>
<
script path="./Home.js" />
<
/script>
<
/behaviors>
<
/page>
複製程式碼
上面的JS程式碼$el.dataContext = new Home();
就是為page
元素設定資料來源,並且這時候,RootDataContext
就指向Home的例項
。
在實際的開發過程中你不會直接接觸RootDataContext
,而是通過事件
、繫結
等形式間接的接觸。你可以在資料繫結的表示式
、事件表示式
中通過this指標
來訪問。比如:
<
lable text="按鈕" event-tap="js:this.onClicked()"/>
複製程式碼
這樣,在tap
事件中,繫結了一個JS回撥,this.onClicked()
指向classHome
的onClicked
方法。
而有的時候你可能需要直接在事件回撥中修改元素的屬性,那麼可以通過$el
來訪問該元素本身,然後設定屬性。比如:
<
lable text="按鈕" event-tap="js:$el.text = 'i clicked'"/>
複製程式碼
這樣,當該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
中。可以參考這篇文章