亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

【quickhybrid】JS端的項(xiàng)目實(shí)現(xiàn)

CHENGKANG / 2743人閱讀

摘要:前言實(shí)現(xiàn)階段之端的實(shí)現(xiàn),重點(diǎn)描述這個(gè)項(xiàng)目的端都有些什么內(nèi)容,是如何實(shí)現(xiàn)的。這個(gè)項(xiàng)目中,基于進(jìn)行單元測(cè)試,而且并不是測(cè)試驅(qū)動(dòng),而是在確定好內(nèi)容后,對(duì)核心部分的代碼都進(jìn)行單測(cè)。

前言

API實(shí)現(xiàn)階段之JS端的實(shí)現(xiàn),重點(diǎn)描述這個(gè)項(xiàng)目的JS端都有些什么內(nèi)容,是如何實(shí)現(xiàn)的。

不同于一般混合框架的只包含JSBridge部分的前端實(shí)現(xiàn),本框架的前端實(shí)現(xiàn)包括JSBridge部分、多平臺(tái)支持,統(tǒng)一預(yù)處理等等。

項(xiàng)目的結(jié)構(gòu)

在最初的版本中,其實(shí)整個(gè)前端庫(kù)就只有一個(gè)文件,里面只規(guī)定著如何實(shí)現(xiàn)JSBridge和原生交互部分。但是到最新的版本中,由于功能逐步增加,單一文件難以滿足要求和維護(hù),因此重構(gòu)成了一整個(gè)項(xiàng)目。

整個(gè)項(xiàng)目基于ES6Airbnb代碼規(guī)范,使用gulp + rollup構(gòu)建,部分重要代碼進(jìn)行了Karma + Mocha單元測(cè)試

整體目錄結(jié)構(gòu)如下:

quickhybrid
    |- dist             // 發(fā)布目錄
    |   |- quick.js
    |   |- quick.h5.js
    |- build            // 構(gòu)建項(xiàng)目的相關(guān)代碼
    |   |- gulpfile.js
    |   |- rollupbuild.js
    |- src              // 核心源碼
    |   |- api          // 各個(gè)環(huán)境下的api實(shí)現(xiàn) 
    |   |   |- h5       // h5下的api
    |   |   |- native   // quick下的api
    |   |- core         // 核心控制
    |   |   |- ...      // 將核心代碼切割為多個(gè)文件
    |   |- inner        // 內(nèi)部用到的代碼
    |   |- util         // 用到的工具類
    |- test             // 單元測(cè)試相關(guān)
    |   |- unit         
    |   |   |- karma.xxx.config.js
    |   |- xxx.spec.js
    |   |- ...

代碼架構(gòu)

項(xiàng)目代中將核心代碼和API實(shí)現(xiàn)代碼分開,核心代碼相當(dāng)于一個(gè)處理引擎,而各個(gè)環(huán)境下的不同API實(shí)現(xiàn)可以多帶帶掛載(這里是為了方便其它地方組合不同環(huán)境下的API所以才分開的,實(shí)際上可以將native和核心代碼打包到一起)

quick.js
quick.h5.js
quick.native.js

這里需要注意,quick.xx環(huán)境.js中的代碼是基于quick.js核心代碼的(譬如里面需要用到一些特點(diǎn)的快速調(diào)用底層的方法)

而其中最核心的quick.js代碼架構(gòu)如下

index
    |- os               // 系統(tǒng)判斷相關(guān)
    |- promise          // promise支持,這里并沒(méi)有重新定義,而是判斷環(huán)境中是否已經(jīng)支持來(lái)決定是否支持
    |- error            // 統(tǒng)一錯(cuò)誤處理
    |- proxy            // API的代理對(duì)象,內(nèi)部對(duì)進(jìn)行統(tǒng)一預(yù)處理,如默認(rèn)參數(shù),promise支持等
    |- jsbridge         // 與native環(huán)境下原生交互的橋梁
    |- callinner        // API的默認(rèn)實(shí)現(xiàn),如果是標(biāo)準(zhǔn)的API,可以不傳入runcode,內(nèi)部默認(rèn)采用這個(gè)實(shí)現(xiàn)
    |- defineapi        // API的定義,API多平臺(tái)支撐的關(guān)鍵,也約定著該如何拓展
    |- callnative       // 定義一個(gè)調(diào)用通用native環(huán)境API的方法,拓展組件API(自定義)時(shí)需要這個(gè)方法調(diào)用
    |- init             // 里面定義config,ready,error的使用
    |- innerUtil        // 給核心文件綁定一些內(nèi)部工具類,供不同API實(shí)現(xiàn)中使用

可以看到,核心代碼已經(jīng)被切割成很小的單元了,雖然說(shuō)最終打包起來(lái)總共代碼也沒(méi)有多少,但是為了維護(hù)性,簡(jiǎn)潔性,這種拆分還是很有必要的

統(tǒng)一的預(yù)處理

在上一篇API多平臺(tái)的支撐中有提到如何基于Object.defineProperty實(shí)現(xiàn)一個(gè)支持多平臺(tái)調(diào)用的API,實(shí)現(xiàn)起來(lái)的API大致是這樣子的

Object.defineProperty(apiParent, apiName, {
    configurable: true,
    enumerable: true,
    get: function proxyGetter() {
        // 確保get得到的函數(shù)一定是能執(zhí)行的
        const nameSpaceApi = proxysApis[finalNameSpace];

        // 得到當(dāng)前是哪一個(gè)環(huán)境,獲得對(duì)應(yīng)環(huán)境下的代理對(duì)象
        return nameSpaceApi[getCurrProxyApiOs(quick.os)] || nameSpaceApi.h5;
    },
    set: function proxySetter() {
        alert("不允許修改quick API");
    },
});

...

quick.extendModule("ui", [{
    namespace: "alert",
    os: ["h5"],
    defaultParams: {
        message: "",
    },
    runCode(message) {
        alert("h5-" + message);
    },
}]);

其中nameSpaceApi.h5的值是api.runCode,也就是說(shuō)直接執(zhí)行runCode(...)中的代碼

僅僅這樣是不夠的,我們需要對(duì)調(diào)用方法的輸入等做統(tǒng)一預(yù)處理,因此在這里,我們基于實(shí)際的情況,在此基礎(chǔ)上進(jìn)一步完善,加上統(tǒng)一預(yù)處理機(jī)制,也就是

const newProxy = new Proxy(api, apiRuncode);

Object.defineProperty(apiParent, apiName, {
    ...
    get: function proxyGetter() {
        ...
        return newProxy.walk();
    }
});

我們將新的運(yùn)行代碼變?yōu)橐粋€(gè)代理對(duì)象Proxy,代理api.runCode,然后在get時(shí)返回代理過(guò)后的實(shí)際方法(.walk()方法代表代理對(duì)象內(nèi)部會(huì)進(jìn)行一次統(tǒng)一的預(yù)處理)

代理對(duì)象的代碼如下

function Proxy(api, callback) {
    this.api = api;
    this.callback = callback;
}

Proxy.prototype.walk = function walk() {
    // 實(shí)時(shí)獲取promise
    const Promise = hybridJs.getPromise();

    // 返回一個(gè)閉包函數(shù)
    return (...rest) = >{
        let args = rest;

        args[0] = args[0] || {};
        // 默認(rèn)參數(shù)的處理
        if (this.api.defaultParams && (args[0] instanceof Object)) {
            Object.keys(this.api.defaultParams).forEach((item) = >{
                if (args[0][item] === undefined) {
                    args[0][item] = this.api.defaultParams[item];
                }
            });
        }

        // 決定是否使用Promise
        let finallyCallback;

        if (this.callback) {
            // 將this指針修正為proxy內(nèi)部,方便直接使用一些api關(guān)鍵參數(shù)
            finallyCallback = this.callback;
        }

        if (Promise) {
            return finallyCallback && new Promise((resolve, reject) = >{
                // 拓展 args
                args = args.concat([resolve, reject]);
                finallyCallback.apply(this, args);
            });
        }

        return finallyCallback && finallyCallback.apply(this, args);
    };
};

從源碼中可以看到,這個(gè)代理對(duì)象統(tǒng)一預(yù)處理了兩件事情:

1.對(duì)于合法的輸入?yún)?shù),進(jìn)行默認(rèn)參數(shù)的匹配

2.如果環(huán)境中支持Promise,那么返回Promise對(duì)象并且參數(shù)的最后加上resolve,reject

而且,后續(xù)如果有新的統(tǒng)一預(yù)處理(調(diào)用API前的預(yù)處理),只需在這個(gè)代理對(duì)象的這個(gè)方法中增加即可

JSBridge解析規(guī)則

前面的文章中有提到JSBridge的實(shí)現(xiàn),但那時(shí)其實(shí)更多的是關(guān)注原理層面,那么實(shí)際上,定義的交互解析規(guī)則是什么樣的呢?如下

// 以u(píng)i.toast實(shí)際調(diào)用的示例
// `${CUSTOM_PROTOCOL_SCHEME}://${module}:${callbackId}/${method}?${params}`
const uri = "QuickHybridJSBridge://ui:9527/toast?{"message":"hello"}";

if (os.quick) {
    // 依賴于os判斷
    if (os.ios) {
        // ios采用
        window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(uri);
    } else {
        window.top.prompt(uri, "");
    }
} else {
    // 瀏覽器
    warn(`瀏覽器中jsbridge無(wú)效, 對(duì)應(yīng)scheme: ${uri}`);
}

原生容器中接收到對(duì)于的uri后反解析即可知道調(diào)用了些什么,上述中:

QuickHybridJSBridge是本框架交互的scheme標(biāo)識(shí)

modulemethod分別代表API的模塊名和方法名

params是對(duì)于方法傳遞的額外參數(shù),原生容器會(huì)解析成JSONObject

callbackId是本次API調(diào)用在H5端的回調(diào)id,原生容器執(zhí)行完后,通知H5時(shí)會(huì)傳遞回調(diào)id,然后H5端找到對(duì)應(yīng)的回調(diào)函數(shù)并執(zhí)行

為什么要用uri的方式,因?yàn)檫@種方式可以兼容以前的scheme方式,如果方案切換,變動(dòng)代價(jià)下(本身就是這樣升級(jí)上來(lái)的,所以沒(méi)有替換的必要)

UA約定

混合開發(fā)容器中,需要有一個(gè)UA標(biāo)識(shí)位來(lái)判斷當(dāng)前系統(tǒng)。

這里Android和iOS原生容器統(tǒng)一在webview中加上如下UA標(biāo)識(shí)(也就是說(shuō),如果容器UA中有這個(gè)標(biāo)識(shí)位,就代表是quick環(huán)境-這也是os判斷的實(shí)現(xiàn)原理)

String ua = webview.getSettings().getUserAgentString();

ua += " QuickHybridJs/" + getVersion();

// 設(shè)置瀏覽器UA,JS端通過(guò)UA判斷是否屬于quick環(huán)境
webview.getSettings().setUserAgentString(ua);
// 獲取默認(rèn)UA
NSString *defaultUA = [[UIWebView new] stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
        
NSString *version = [[NSBundle mainBundle].infoDictionary objectForKey:@"CFBundleShortVersionString"];
        
NSString *customerUA = [defaultUA stringByAppendingString:[NSString stringWithFormat:@" QuickHybridJs/%@", version]];
        
[[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent":customerUA}];
        

如上述代碼中分別在Android和iOS容器的UA中添加關(guān)鍵性的標(biāo)識(shí)位。

API內(nèi)部做了些什么

API內(nèi)部只做與本身功能邏輯相關(guān)的操作,這里有幾個(gè)示例

quick.extendModule("ui", [{
    namespace: "toast",
    os: ["h5"],
    defaultParams: {
        message: "",
    },
    runCode(...rest) {
        // 兼容字符串形式
        const args = innerUtil.compatibleStringParamsToObject.call(this, rest, "message", );
        const options = args[0];
        const resolve = args[1];
        
        // 實(shí)際的toast實(shí)現(xiàn)
        toast(options);
        options.success && options.success();
        resolve && resolve();
    },
}, ...]);
quick.extendModule("ui", [{
    namespace: "toast",
    os: ["quick"],
    defaultParams: {
        message: "",
    },
    runCode(...rest) {
        // 兼容字符串形式
        const args = innerUtil.compatibleStringParamsToObject.call(this, rest, "message");

        quick.callInner.apply(this, args);
    },
}, ...]);

以上是toast功能在h5和quick環(huán)境下的實(shí)現(xiàn),其中,在quick環(huán)境下唯一做的就是兼容了一個(gè)字符串形式的調(diào)用,在h5環(huán)境下則是完全的實(shí)現(xiàn)了h5下對(duì)應(yīng)的功能(promise也需自行兼容)

為什么h5中更復(fù)雜?因?yàn)閝uick環(huán)境中,只需要拼湊成一個(gè)JSBridge命令發(fā)送給原生即可,具體功能由原生實(shí)現(xiàn),而h5的實(shí)現(xiàn)是需要自己完全實(shí)現(xiàn)的。

另外,其實(shí)在quick環(huán)境中,上述還不是最少的代碼(上述加了一個(gè)兼容調(diào)用功能,所以多了幾行),最少代碼如下

quick.extendModule("ui", [{
    namespace: "confirm",
    os: ["quick"],
    defaultParams: {
        title: "",
        message: "",
        buttonLabels: ["取消", "確定"],
    },
}, ...]);

可以看到,只要是符合標(biāo)準(zhǔn)的API定義,在quick環(huán)境下的實(shí)現(xiàn)只需要定義些默認(rèn)參數(shù)就可以了,其它的框架自動(dòng)幫助實(shí)現(xiàn)了(同樣promise的實(shí)現(xiàn)也在內(nèi)部默認(rèn)處理掉了)

這樣以來(lái),就算是標(biāo)準(zhǔn)quick環(huán)境下的API數(shù)量多,實(shí)際上增加的代碼也并不多。

關(guān)于代碼規(guī)范與單元測(cè)試

項(xiàng)目中采用的Airbnb代碼規(guī)范并不是100%契合原版,而是基于項(xiàng)目的情況定制了下,但是總體上95%以上是符合的

還有一塊就是單元測(cè)試,這是很容易忽視的一塊,但是也挺難做好的。這個(gè)項(xiàng)目中,基于Karma + Mocha進(jìn)行單元測(cè)試,而且并不是測(cè)試驅(qū)動(dòng),而是在確定好內(nèi)容后,對(duì)核心部分的代碼都進(jìn)行單測(cè)。
內(nèi)部對(duì)于API的調(diào)用基本都是靠JS來(lái)模擬,對(duì)于一些特殊的方法,還需Object.defineProperty(window.navigator, name, prop)來(lái)改變window本身的屬性來(lái)模擬。
本項(xiàng)目中的核心代碼已經(jīng)達(dá)到了100%的代碼覆蓋率。

具體的代碼這里不贅述,可以參考源碼

返回根目錄

【quickhybrid】如何實(shí)現(xiàn)一個(gè)Hybrid框架

源碼

github上這個(gè)框架的實(shí)現(xiàn)

quickhybrid/quickhybrid

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/90463.html

相關(guān)文章

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<