iOS混合開發庫(GICXMLLayout)7、JavaScript篇

介紹

GIC從0.3.0版本開始正式支持JavaScript,也就意味你能夠直接使用JavaScript來寫業務邏輯,至此開始,結合XMLjs文件圖片資源等靜態文件,徹底能夠將整個的APP作成一個能夠熱更新的應用。另外,在開發的時候也能夠經過HotReload的方式,無需編譯整個APP就能實時刷新應用,進一步的加快應用的開發效率。javascript

在最新的0.4.8版本中,GIC已經支持大部分的ES6特性,包括但不限於yieldgeneratorPromise等等,而且也支持了ES8中的asyncawaithtml

GIC在最初的架構中根本就沒有考慮對JavaScript提供支持,數據綁定事件綁定等等功能通通都是爲native code設計的。然後來當我考慮想要對JavaScript進行支持的時候,也僅僅是經過behavior來實現。事實上,若是你看過GIC的源碼,你會發現GIC能夠實現對任意腳本的支持,而實現的方式也僅僅只是經過自定義behavior來實現。從這一點來講,側面反映了behavior功能的強大之處。java

GIC對於JavaScript的支持是基於JavaScriptCore這個蘋果官方framework實現的,其實這個framework原本就是開源的,並且仍是屬於大名鼎鼎的Webkit其中的模塊。再加上Webkit的跨平臺能力,也就意味着只要是基於JavaScriptCore開發的功能,一樣能夠移植到安卓上面,也就是意味着GIC在對JavaScript的支持上具有了跨平臺的潛力node

對ES6的支持

首先各位要知道的是,iOS對於Es6規範的支持程度在不一樣的iOS版本中是不一樣的,iOS8對於ES6是徹底不支持的,iOS9部分支持,最新的iOS12基本已經徹底支持了ES6的規範。固然,這裏就不列出不一樣的iOS版本具體支持哪些ES6特性,你只須要知道,不一樣的iOS版本對於不一樣的ES6規範支持程度不一就好了。ios

然而咱們的app確定是要運行在低版本上的,那麼如何解決這個問題?git

事實上,GIC自己並無提供原生的解決方案,雖然也嘗試過內置babel來實現實時轉碼,可是後來發現性能太差,對於複雜的JS文件進行轉碼會耗費大量的CPU資源。所以就斷了內置babel的想法。github

可是GIC經過爲VSCode開發插件的方法,曲線實現了對ES6的完整支持,VSCode的插件(GICVSCodeExtension)能夠一次性將整個工程的JS代碼編譯成ES5的代碼。也就意味你能夠放心的在你的工程中編寫ES6的代碼,而後經過VSCode進行編譯,進而使得你的JS代碼能夠運行在不一樣的iOS版本上。web

事實上,VSCode插件也是經過babel來轉碼的,而且GIC也自定義了babel插件。objective-c

你只須要經過GIC提供的腳手架來建立項目就能得到這樣的能力,詳細的腳手架以及IDE支持的介紹能夠查看這篇文章npm

在最新的0.4.8版本中,新增了對yieldgeneratorPromiseasyncawait的支持。

require

GIC中,打開一個新頁面至關於在瀏覽器中打開一個新頁面,頁面跟頁面之間並不能共享JavaScript執行環境(JSContext),每一個頁面都有一個獨立的sand box(JSContext)。所以,當一個頁面須要某個功能的時候你須要經過require方法手動的將這個功能引入,每一個頁面都是如此,同一個功能若是在不一樣的頁面中使用,那麼意味着你須要在不一樣的頁面單獨引入。

當前require的功能更多的相似Nodejs的用法,若是你之前接觸過nodejs的話,那麼很容易理解。

其實,GIC提供的require函數相較於Nodejs中的require函數來講只是一個簡化版的實現,功能比較簡單。

使用module.exports來導出,使用require函數來導入,當前require函數支持jsjson文件的導入。

好比: 在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舉例

  1. 下載axios的JS文件。

    地址以下:unpkg.com/axios@0.18.…

  2. 將下載的JS文件拷貝到工程的js文件夾下面。
  3. 使用require函數以非module模式引入axios,
    require('./axios.js', false);
    複製代碼
  4. 直接在代碼中使用
    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擴展。

上面已經提過,GICJavaScript的支持,是經過JavaScriptCore來實現的。具體一點是經過JSContextJSValue來實現的。然而JavaScriptCore自己提供的API是有限的,好比consoleXMLHttpRequestsetTimeout等API是沒有的,只能經過擴展來實現。GIC已經提供了一些JavaScriptCore所沒有的API,而其餘的API就須要你本身來實現了。

然而對於目前不少從npm安裝的庫來講,很遺憾,GIC不支持。可是你可使用直接加載js文件的方式來引入您的工程,可是注意這些庫有可能用到了其餘的一些api,那麼這時候就須要你本身擴展實現這些API以便提供支持。

其實對於第三方庫的支持已經跟GIC自己沒有關係了,徹底能夠經過擴展+後期編譯的方式來實現支持。不過這樣的工程會比較大,你不可能作到對全部庫的支持,只能針對特定庫作有限的支持。

JSValue注意事項。

在實際的項目開發過程當中,免不了要自定義JSAPI的擴展,而實現JSAPI的擴展那麼你就回避不了對JSValue的使用,可是JSValue仍是有些地方須要注意的,不然會爲你的代碼埋下內存管理的隱患。

各位在看這篇博客以前若是沒有接觸過JavaScriptCore相關的內容,我仍是建議各位先去了解下JavaScriptCore,尤爲是裏面的JSContextJSValue

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 private="true"> $el.dataContext = new Home(); </script>
    </behaviors>
</page>
複製代碼

上面的JS代碼$el.dataContext = new Home();就是爲page元素設置數據源,而且這時候,RootDataContext就指向Home的實例

在實際的開發過程當中你不會直接接觸RootDataContext,而是經過事件綁定等形式間接的接觸。你能夠在數據綁定的表達式事件表達式中經過this指針來訪問。好比:

<lable text="按鈕" event-tap="js:this.onClicked()"/>
複製代碼

這樣,在tap事件中,綁定了一個JS回調,this.onClicked()指向classHomeonClicked方法。

而有的時候你可能須要直接在事件回調中修改元素的屬性,那麼能夠經過$el來訪問該元素自己,而後設置屬性。好比:

<lable text="按鈕" event-tap="js:$el.text = 'i clicked'"/>
複製代碼

這樣,當該lable被點擊的時候,文本內容就會被修改爲i clicked

事實上,上面在爲page設置數據源的時候,就是經過$el來設置的。

yield、generator

GIC在最新的版本中已經提供了對yieldgenerator的支持,並且generator是由native code開發的,並非babel轉碼支持的,babel轉碼只能提供對yield的支持,可是generator轉碼事後就沒法在iOS上運行了,所以我參考了generator的API,以JSAPI擴展的方式,用objective-c實現了generator整套API。實現過程我後面準備單寫一篇博客來介紹如何實現。

GIC也已經提供了對Promise的支持,所以在Promiseyield的基礎上,提供對asyncawait支持就變得瓜熟蒂落了。事實上,GIC已經支持ES8中的asyncawait功能了。

數據綁定

GIC自己是支持數據綁定的,在引入JavaScript後也提供了數據綁定的功能。可是JS的數據綁定跟naitve coed的數據綁定在實現方式上是不同的,GIC在實現JS的數據綁定過程當中,充分參考了VUE中的實現方式,而且研究了VUE的源碼,最終將VUE的實現方式移植到了GIC中。能夠參考這篇文章

相關文章
相關標籤/搜索