前言
在框架規劃時,就有提到過這個框架的一些常用功能需要支援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都是有quick
,h5
環境下的實現的。
多平臺支撐的核心
從上述的介紹中也可以看到,多平臺支撐主要是前端的實現,與原生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框架中,預設支援quick
和h5
有種環境,核心程式碼就是上述列舉的(當然,內部增加了一些代理,預設引數處理等,會稍微複雜一點)。
基於這個核心,然後可以將框架的定義和API定義分開打包
quick.js
quick.h5.js
這樣,最終看起來h5
下的API定義就是一個擴充包,是沒有它也不會影響quick環境下的使用,而且,如果增加一個新的環境(比如dd),
只需要再新增另一個環境的擴充包而已,各種寫法都是一樣的,這樣便於了統一維護
返回根目錄
原始碼
github
上這個框架的實現