【quickhybrid】API多平臺支撐的實現

来源:http://www.cnblogs.com/dailc/archive/2017/12/25/8107881.html
-Advertisement-
Play Games

前言 在框架規劃時,就有提到過這個框架的一些常用功能需要支持 環境下的調用,也就是需要實現API的多平臺支撐 為什麼要多平臺支撐?核心仍然是復用代碼,比如在微信下,在釘釘下,在quick容器下, 如果沒有多平臺支撐,那麼 只能用於quick容器下,釘釘和微信下就得分別用其它代碼實現, 代碼復用率低, ...


前言

在框架規劃時,就有提到過這個框架的一些常用功能需要支持H5環境下的調用,也就是需要實現API的多平臺支撐

為什麼要多平臺支撐?核心仍然是復用代碼,比如在微信下,在釘釘下,在quick容器下,
如果沒有多平臺支撐,那麼quick.ui.alert只能用於quick容器下,釘釘和微信下就得分別用其它代碼實現,
代碼復用率低,如果實現了多平臺支撐。那麼三個平臺中同一個功能的代碼則是一樣的。

什麼樣的多平臺支撐

當然了,本框架實現的多平臺支撐和一般框架的有點區別。

一般的框架中支持多個平臺更多的是一個polyfill,譬如

// 假設以前不支持h5
const oldToast = quick.ui.toast;

quick.ui.toast = function(...) {
    if (os.h5) {
        // 做一些h5中做的
        ...
    } else {
        oldToast(...);
    }
};

這就是墊片實現,如果是新的環境,用新的實現,否則用老的實現

而__本框架中的多平臺實現是直接內置到了框架核心中__,也就是說框架本身就支持多平臺API的設置

quick.extendModule('ui', [{
    namespace: 'toast',
    os: ['h5'],
    defaultParams: {
        message: '',
    },
    runCode(...rest) {
        // 定義h5環境中的做法
        ...
    },
}, ...];

quick.extendModule('ui', [{
    namespace: 'toast',
    os: ['quick'],
    defaultParams: {
        message: '',
    },
    runCode(...rest) {
        // 定義quick環境中的做法
        ...
    },
}, ...];

在框架內部定義API時,不再是直接的quick.ui.alert = xxx,而是通過特定的API單獨給某個環境下定義實現

而且,框架中的定義,每一個API都是有quickh5環境下的實現的。

多平臺支撐的核心

從上述的介紹中也可以看到,多平臺支撐主要是前端的實現,與原生API,原生API在這裡面只能算一個環境下的實現

核心就是基於:Object.defineProperty,重寫set和get

Object.defineProperty(apiParent, apiName, {
    configurable: true,
    enumerable: true,
    get: function proxyGetter() {
        // 需要根據不同的環境,返回對應下的內容
        ...
    },
    set: function proxySetter() {
        // 可以提示禁止修改API
    },
});

本框架中的多平臺實現代碼可以參考源碼,這裡不贅述,下文中會介紹如何簡單的實現一個多平臺支撐API

實現一個多平臺支撐API

我們先預設最終的結果:

quick.os.quick = true;
quick.ui.alert('hello'); // quick-hello

quick.os.quick = false;
quick.ui.alert('hello'); // h5-hello

quick.ui.alert = 11; // 提示:不允許修改quick API

那麼要達到上述的要求,應該如何做呢?

寫一個雛形

最簡單的,先假設這些實現都已經存在,然後直接基於defineProperty返回

function alertH5(message) {
    alert('h5-' + message);
}
function alertQuick(message) {
    alert('quick-' + message);
}
const quick = {};

quick.ui = {};
quick.os = {
    quick: false,
};

Object.defineProperty(quick.ui, 'alert', {
    configurable: true,
    enumerable: true,
    get: function proxyGetter() {
        // 需要根據不同的環境,返回對應下的內容
        if (quick.os.quick) {
            return alertQuick;
        } else {
            return alertH5;
        }
    },
    set: function proxySetter() {
        // 可以提示禁止修改API
        alert('不允許修改quick API');
    },
});

那麼,它的調用結果是

quick.os.quick = true;
quick.ui.alert('hello'); // quick-hello

quick.os.quick = false;
quick.ui.alert('hello'); // h5-hello

quick.ui.alert = 11; // 提示:不允許修改quick API

雖然效果和預設的一樣,但是很明顯還需優化完善

增加拓展API的方法

拓展方式的定義如下

const quick = {};

quick.os = {
    quick: false,
};
/**
 * 存放所有的代理 api對象
 * 每一個命名空間下的每一個os都可以執行
 * proxyapi[namespace][os]
 */
const proxysApis = {};
// 支持的所有環境
const supportOsArray = ['quick', 'h5'];

function getCurrProxyApiOs(currOs) {
    for (let i = 0, len = supportOsArray.length; i < len; i += 1) {
        if (currOs[supportOsArray[i]]) {
            return supportOsArray[i];
        }
    }

    // 預設是h5
    return 'h5';
}

// 如獲取quick.ui.alert
function getModuleApiParentByNameSpace(module, namespace) {
    let apiParent = module;
    // 只取命名空間的父級,如果僅僅是xxx,是沒有父級的
    const parentNamespaceArray = /[.]/.test(namespace) ? namespace.replace(/[.][^.]+$/, '').split('.') : [];

    parentNamespaceArray.forEach((item) = >{
        apiParent[item] = apiParent[item] || {};
        apiParent = apiParent[item];
    });

    return apiParent;
}

function proxyApiNamespace(apiParent, apiName, finalNameSpace) {
    // 代理API,將apiParent里的apiName代理到Proxy執行
    Object.defineProperty(apiParent, apiName, {
        configurable: true,
        enumerable: true,
        get: function proxyGetter() {
            // 確保get得到的函數一定是能執行的
            const nameSpaceApi = proxysApis[finalNameSpace];

            // 得到當前是哪一個環境,獲得對應環境下的代理對象
            return nameSpaceApi[getCurrProxyApiOs(quick.os)] || nameSpaceApi.h5;
        },
        set: function proxySetter() {
            alert('不允許修改quick API');
        },
    });
}

function extendApi(moduleName, apiParam) {
    if (!apiParam || !apiParam.namespace) {
        return;
    }

    if (!quick[moduleName]) {
        quick[moduleName] = {};
    }

    const api = apiParam;
    const modlue = quick[moduleName];
    const apiNamespace = api.namespace;
    const apiParent = getModuleApiParentByNameSpace(modlue, apiNamespace);
    // 最終的命名空間是包含模塊的
    const finalNameSpace = moduleName + '.' + apiNamespace;
    // 如果僅僅是xxx,直接取xxx,如果aa.bb,取bb
    const apiName = /[.]/.test(apiNamespace) ? api.namespace.match(/[.][^.]+$/)[0].substr(1) : apiNamespace;

    // 這裡防止觸發代理,就不用apiParent[apiName]了,而是用proxysApis[finalNameSpace]
    if (!proxysApis[finalNameSpace]) {
        // 如果還沒有代理這個API的命名空間,代理之,只需要設置一次代理即可
        proxyApiNamespace(apiParent, apiName, finalNameSpace);
    }

    // 一個新的API代理,會替換以前API命名空間中對應的內容
    const apiRuncode = api.runCode;
    const oldProxyNamespace = proxysApis[finalNameSpace] || {};

    proxysApis[finalNameSpace] = {};

    supportOsArray.forEach((osTmp) = >{
        if (api.os && api.os.indexOf(osTmp) !== -1) {
            // 如果存在這個os,並且合法,重新定義
            proxysApis[finalNameSpace][osTmp] = apiRuncode;
        } else if (oldProxyNamespace[osTmp]) {
            // 否則仍然使用老版本的代理
            proxysApis[finalNameSpace][osTmp] = oldProxyNamespace[osTmp];
        }
    });
}

function extendModule(moduleName, apis) {
    if (!apis || !Array.isArray(apis)) {
        return;
    }
    if (!quick[moduleName]) {
        quick[moduleName] = [];
    }
    for (let i = 0, len = apis.length; i < len; i += 1) {
        extendApi(moduleName, apis[i]);
    }

}

quick.extendModule = extendModule;

上述代碼中增加了些複雜度,有一個統一管理所有代理調用的池,然後每次會更新對於環境下的代理

基於上述的方式可以如下拓展對於環境下的API

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

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

最終的調用如下(結果和預期一致)

quick.os.quick = true;
quick.ui.alert('hello'); // quick-hello
quick.os.quick = false;
quick.ui.alert('hello'); // h5-hello
quick.ui.alert = 11; // 提示:不允許修改quick API

雖然就一兩個API來說,這類拓展方式看起來很複雜,但是當API一多,特別是還需批量預處理時(如預設參數,Promise支持等),它的優勢就出來了

多平臺支撐在quick中的應用

quick hybrid框架中,預設支持quickh5有種環境,核心代碼就是上述列舉的(當然,內部增加了一些代理,預設參數處理等,會稍微複雜一點)。

基於這個核心,然後可以將框架的定義和API定義分開打包

quick.js
quick.h5.js

這樣,最終看起來h5下的API定義就是一個拓展包,是沒有它也不會影響quick環境下的使用,而且,如果增加一個新的環境(比如dd),
只需要再新增另一個環境的拓展包而已,各種寫法都是一樣的,這樣便於了統一維護

返回根目錄

源碼

github上這個框架的實現

quickhybrid/quickhybrid


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文轉載來自標點符的《資料庫中Schema(模式)概念的理解》,原文地址: http://www.biaodianfu.com/database-schema.html 在學習資料庫時,會遇到一個讓人迷糊的Schema的概念。實際上,schema就是資料庫對象的集合,這個集合包含了各種對象如:表、視 ...
  • ...
  • win10 64位 mysql安裝過程出現status顯示failed ...
  • MySQL中使用group_concat()函數長度限制問題,怎麼修改!!! ...
  • 1.1 索引的介紹 索引是對資料庫表中一列或多列的值進行排序的一種結構,使用索引可快速訪問資料庫表中的特定信息。如果想按特定職員的姓來查找他或她,則與在表中搜索所有的行相比,索引有助於更快地獲取信息。 索引的一個主要目的就是加快檢索表中數據的方法,亦即能協助信息搜索者儘快的找到符合限制條件的記錄ID ...
  • 自動佈局是一項強大的功能,它允許開發者創建一個單一的用戶界面,它會自動調整屏幕大小,方向和本地化,Xcode5中的編輯界面的自動佈局功能已經大大增強了。當約束缺失或錯誤配置時,界面生成器可以修複佈局。 參考資料:《iOS7開發快速入門》 ...
  • 轉載請註明原文鏈接:http://www.cnblogs.com/yanyojun/p/8099523.html 代碼已上傳到github:https://github.com/YanYoJun/FragmentTabHostDemo 上一篇有講過使用ViewPager來實現標簽卡效果的。這一篇講一 ...
  • Android遠程桌面助手理論上相容Android4.4至Android8.1之間所有的原生安卓系統,其他第三方ROM,如MIUI、Flyme、EMUI和Smartisan OS等也都陸續測試過,目前尚未發現相容性問題。 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...