摘要:本文源碼為版本。的代碼結(jié)構(gòu)也是一個(gè)很經(jīng)典的定義結(jié)構(gòu)構(gòu)造函數(shù)實(shí)例修改函數(shù)原型共享實(shí)例方法,它提供事件通道上事件的訂閱撤消訂閱調(diào)用。
前言
cordova(PhoneGap) 是一個(gè)優(yōu)秀的經(jīng)典的中間件框架,網(wǎng)上對(duì)其源代碼解讀的文章確實(shí)不多,本系列文章試著解讀一下,以便對(duì)cordova 框架的原理理解得更深入。本文源碼為cordova android版本6.1.2。
源碼結(jié)構(gòu)我們使用IDE的代碼折疊功能先從整體上把握代碼結(jié)構(gòu)。
/* * 版權(quán)申明及注釋部分 */ ;(function() { ... })();
;是保證導(dǎo)入的其它js腳本,使用工具壓縮js文件時(shí)不出錯(cuò)。一個(gè)自執(zhí)行匿名函數(shù)包裹,防止內(nèi)部變量污染到外部命名空間。閱讀過(guò)jQuery源碼的人都知道,jQuery的也是相同的結(jié)構(gòu),只是jQuery定義的匿名函數(shù)多了兩個(gè)參數(shù)window和undefined,然后調(diào)用的時(shí)候只傳入window,這樣,window可以在jQuery內(nèi)部安全使用,而undefined也的確表示未定義(有些瀏覽器實(shí)現(xiàn)允許重定義undefined)。
繼續(xù)展開(kāi)代碼,可以看到如下的結(jié)構(gòu):
;(function() { var PLATFORM_VERSION_BUILD_LABEL = "6.1.2"; // 模塊化系統(tǒng) /* ------------------------------------------------------------- */ var require, // 加載使用module define; // 定義注冊(cè)module // require|define 的邏輯 (function () { ... })(); // Export for use in node if (typeof module === "object" && typeof require === "function") { module.exports.require = require; module.exports.define = define; } /* ------------------------------------------------------------- */ // 事件的處理和回調(diào),外部訪問(wèn)cordova.js的入口 define("cordova", function(require, exports, module) { ... } // JS->Native的具體交互形式 define("cordova/android/nativeapiprovider", function(require, exports, module) { ... } // 通過(guò)prompt()和Native交互 define("cordova/android/promptbasednativeapi", function(require, exports, module) { ... } // 用于plugin中校驗(yàn)參數(shù),比如argscheck.checkArgs("fFO", "Camera.getPicture", arguments); 參數(shù)應(yīng)該是2個(gè)函數(shù)1個(gè)對(duì)象 define("cordova/argscheck", function(require, exports, module) { ... } // JS->Native交互時(shí)對(duì)ArrayBuffer進(jìn)行uint8ToBase64(WebSockets二進(jìn)制流) define("cordova/base64", function(require, exports, module) { ... } // 對(duì)象屬性操作,比如把一個(gè)對(duì)象的屬性Merge到另外一個(gè)對(duì)象 define("cordova/builder", function(require, exports, module) { ... } // 事件通道 define("cordova/channel", function(require, exports, module) { ... } // 執(zhí)行JS->Native交互 define("cordova/exec", function(require, exports, module) { ... } // 用于Plugin中往已經(jīng)有的模塊上添加方法 define("cordova/exec/proxy", function(require, exports, module) { ... } // 初始化處理 define("cordova/init", function(require, exports, module) { ... } define("cordova/init_b", function(require, exports, module) { ... } // 把定義的模塊clobber到一個(gè)對(duì)象,在初始化的時(shí)候會(huì)賦給window define("cordova/modulemapper", function(require, exports, module) { ... } define("cordova/modulemapper_b", function(require, exports, module) { ... } // 平臺(tái)啟動(dòng)處理 define("cordova/platform", function(require, exports, module) { ... } // 清緩存、loadUrl、退出程序等 define("cordova/plugin/android/app", function(require, exports, module) { ... } // 載所有cordova_plugins.js中定義的模塊,執(zhí)行完成后會(huì)觸發(fā) define("cordova/pluginloader", function(require, exports, module) { ... } define("cordova/pluginloader_b", function(require, exports, module) { ... } // 獲取絕對(duì)URL,InAppBrowser中會(huì)用到 define("cordova/urlutil", function(require, exports, module) { ... } // 工具類 define("cordova/utils", function(require, exports, module) { ... } // 所有模塊注冊(cè)完之后,導(dǎo)入cordova至全局環(huán)境中 window.cordova = require("cordova"); // 初始化啟動(dòng) require("cordova/init"); })();
從上可以清晰的看出,在cordova內(nèi)部,首先是定義了兩個(gè)公共的require和define函數(shù),然后是使用define注冊(cè)所有模塊,再通過(guò)window.cordova=require("cordova")導(dǎo)入庫(kù)文件至全局執(zhí)行環(huán)境中。
模塊機(jī)制類似于Java的package/import,在JavaScript中也有類似的define/require,它用來(lái)異步加載module化的js,從而提高運(yùn)行效率。模塊化加載的必要性,起源于nodejs的出現(xiàn)。但是JavaScript并沒(méi)有內(nèi)置模塊系統(tǒng),所以就出現(xiàn)了很多規(guī)范。?主要有2種:CommonJS 和 AMD(Asynchronous Module Definition)。還有國(guó)內(nèi)興起的CMD(Common Module Definition)?。CommonJS主要面對(duì)的是服務(wù)器,代表是Node.js;AMD針對(duì)瀏覽器進(jìn)行了優(yōu)化,主要實(shí)現(xiàn)require.js;CMD是seajs。?
cordova-js最開(kāi)始采用的是require.js作者寫的almond.js(兼容AMD和CommonJS),但之后由于特殊需求(比如模塊不存在的時(shí)候要throw異常),最終從almond.js fork過(guò)來(lái)實(shí)現(xiàn)了一個(gè)簡(jiǎn)易CommonJS風(fēng)格的模塊系統(tǒng),同時(shí)提供了和nodejs之間很好的交互。在cordova.js中可以直接使用define()和require(),在其他文件可以通過(guò)cordova.define()和cordova.require()來(lái)調(diào)用。所以src/scripts/require.js中定義的就是一個(gè)精簡(jiǎn)的JavaScript模塊系統(tǒng)。?
// cordova.js內(nèi)部使用的全局函數(shù)require/define var require, define; (function () { // 初始化一個(gè)空對(duì)象,緩存所有的模塊 var modules = {}, // 正在build中的模塊ID的棧 requireStack = [], // 標(biāo)示正在build中模塊ID的Map inProgressModules = {}, SEPARATOR = "."; // 模塊build function build(module) { // 備份工廠方法 var factory = module.factory, // 對(duì)require對(duì)象進(jìn)行特殊處理 localRequire = function (id) { var resultantId = id; //Its a relative path, so lop off the last portion and add the id (minus "./") if (id.charAt(0) === ".") { resultantId = module.id.slice(0, module.id.lastIndexOf(SEPARATOR)) + SEPARATOR + id.slice(2); } return require(resultantId); }; // 給模塊定義一個(gè)空的exports對(duì)象,防止工廠類方法中的空引用 module.exports = {}; // 刪除工廠方法 delete module.factory; // 調(diào)用備份的工廠方法(參數(shù)必須是require,exports,module) factory(localRequire, module.exports, module); // 返回工廠方法中實(shí)現(xiàn)的module.exports對(duì)象 return module.exports; } // 加載使用模塊 require = function (id) { // 如果模塊不存在拋出異常 if (!modules[id]) { throw "module " + id + " not found"; // 如果模塊正在build中拋出異常 } else if (id in inProgressModules) { var cycle = requireStack.slice(inProgressModules[id]).join("->") + "->" + id; throw "Cycle in require graph: " + cycle; } // 如果模塊存在工廠方法說(shuō)明還未進(jìn)行build(require嵌套) if (modules[id].factory) { try { // 標(biāo)示該模塊正在build inProgressModules[id] = requireStack.length; // 將該模塊壓入請(qǐng)求棧 requireStack.push(id); // 模塊build,成功后返回module.exports return build(modules[id]); } finally { // build完成后刪除當(dāng)前請(qǐng)求 delete inProgressModules[id]; requireStack.pop(); } } // build完的模塊直接返回module.exports return modules[id].exports; }; // 定義注冊(cè)模塊 define = function (id, factory) { // 如果已經(jīng)存在拋出異常 if (modules[id]) { throw "module " + id + " already defined"; } // 模塊以ID為索引包含ID和工廠方法 modules[id] = { id: id, factory: factory }; }; // 移除模塊 define.remove = function (id) { delete modules[id]; }; // 返回所有模塊 define.moduleMap = modules; })();
首先在外部cordova環(huán)境中定義require和define兩個(gè)變量,用來(lái)存儲(chǔ)實(shí)現(xiàn)導(dǎo)入功能的函數(shù)和實(shí)現(xiàn)注冊(cè)功能的函數(shù)。然后用一個(gè)立即調(diào)用的匿名函數(shù)來(lái)實(shí)例化這兩個(gè)變量,在這個(gè)匿名函數(shù)內(nèi)部,緩存了所有的功能模塊。注冊(cè)模塊時(shí),如果已經(jīng)注冊(cè)了,就直接拋出異常,防止無(wú)意中重定義,如確實(shí)需要重定義,可先調(diào)用define.remove。
從內(nèi)部私有函數(shù)build中,可以看出,調(diào)用工廠函數(shù)時(shí), factory(localRequire, module.exports, module); 第一個(gè)參數(shù)localRequire實(shí)質(zhì)還是調(diào)用全局的require()函數(shù),只是把ID稍微加工了一下支持相對(duì)路徑。cordova.js沒(méi)有用到相對(duì)路徑的require,但在一些Plugin的js中有,比如Contact.js 中 ContactError = require("./ContactError");
這里我們寫個(gè)測(cè)試用例:
注:module.js為上述cordova的模塊代碼。
上面例子中我們定義了兩個(gè)模塊,這里是寫在同一個(gè)頁(yè)面下,在實(shí)際中我們自然希望寫在兩個(gè)不同的文件中,然后按需加載。我們上一篇文章中說(shuō)明了cordova的插件使用方法,我們會(huì)發(fā)現(xiàn)cordova_plugins.js中定義了cordova插件的id、路徑等變量,并且該文件定義了一個(gè)id為cordova/plugin_list的模塊,我們?cè)赾ordova.js中可以看到有這個(gè)模塊的引用。
定義了require和define并賦值后,是將cordova所有模塊一一注冊(cè),例如:
define("cordova",function(require,exports,module){ // 工廠函數(shù)內(nèi)部實(shí)現(xiàn)代碼 });
這里需要注意的是,define只是注冊(cè)模塊,不會(huì)調(diào)用其factory。factory函數(shù)在這個(gè)時(shí)候并沒(méi)有實(shí)際執(zhí)行,而只是定義,并作為一個(gè)參數(shù)傳遞給define函數(shù)。所有模塊注冊(cè)完之后,通過(guò):
window.cordova = require("cordova");
導(dǎo)入至全局環(huán)境。
因?yàn)槭亲?cè)后第一次導(dǎo)入,所以在執(zhí)行require("cordova")時(shí),modules["cordova"].factory的值是注冊(cè)時(shí)的工廠函數(shù),轉(zhuǎn)變?yōu)閎oolean值時(shí)為true,從而在這里會(huì)通過(guò)build調(diào)用這個(gè)工廠函數(shù),并將這個(gè)工廠函數(shù)從注冊(cè)緩存里面刪除,接下來(lái)的就是去執(zhí)行cordova的這個(gè)factory函數(shù)了。
事件通道作為觀察者模式(Observer)的一種變形,很多MV*框架(比如:Vue.js、Backbone.js)中都提供發(fā)布/訂閱模型來(lái)對(duì)代碼進(jìn)行解耦。cordova.js中也提供了一個(gè)自定義的pub-sub模型,基于該模型提供了一些事件通道,用來(lái)控制通道中的事件什么時(shí)候以什么樣的順序被調(diào)用,以及各個(gè)事件通道的調(diào)用。
src/common/channel.js的代碼結(jié)構(gòu)也是一個(gè)很經(jīng)典的定義結(jié)構(gòu)(構(gòu)造函數(shù)、實(shí)例、修改函數(shù)原型共享實(shí)例方法),它提供事件通道上事件的訂閱(subscribe)、撤消訂閱(unsubscribe)、調(diào)用(fire)。pub-sub模型用于定義和控制對(duì)cordova初始化的事件的觸發(fā)以及此后的自定義事件。
頁(yè)面加載和Cordova啟動(dòng)期間的事件順序如下:
onDOMContentLoaded ——(內(nèi)部事件通道)頁(yè)面加載后DOM解析完成
onNativeReady ——(內(nèi)部事件通道)Cordova的native準(zhǔn)備完成
onCordovaReady ——(內(nèi)部事件通道)所有Cordova的JavaScript對(duì)象被創(chuàng)建完成可以開(kāi)始加載插件
onDeviceReady —— Cordova全部準(zhǔn)備完成
onResume —— 應(yīng)用重新返回前臺(tái)
onPause —— 應(yīng)用暫停退到后臺(tái)
可以通過(guò)下面的事件進(jìn)行監(jiān)聽(tīng):
document.addEventListener("deviceready", myDeviceReadyListener, false); document.addEventListener("resume", myResumeListener, false); document.addEventListener("pause", myPauseListener, false);
DOM生命周期事件應(yīng)用于保存和恢復(fù)狀態(tài):
window.onload
window.onunload
define("cordova/channel", function(require, exports, module) { var utils = require("cordova/utils"), nextGuid = 1; // 事件通道的構(gòu)造函數(shù) var Channel = function(type, sticky) { // 通道名稱 this.type = type; // 通道上的所有事件處理函數(shù)Map(索引為guid) this.handlers = {}; // 通道的狀態(tài)(0:非sticky, 1:sticky但未調(diào)用, 2:sticky已調(diào)用) this.state = sticky ? 1 : 0; // 對(duì)于sticky事件通道備份傳給fire()的參數(shù) this.fireArgs = null; // 當(dāng)前通道上的事件處理函數(shù)的個(gè)數(shù) this.numHandlers = 0; // 訂閱第一個(gè)事件或者取消訂閱最后一個(gè)事件時(shí)調(diào)用自定義的處理 this.onHasSubscribersChange = null; }, // 事件通道外部接口 channel = { // 把指定的函數(shù)h訂閱到c的各個(gè)通道上,保證h在每個(gè)通道的最后被執(zhí)行 join: function(h, c) { var len = c.length, i = len, f = function() { if (!(--i)) h(); }; for (var j=0; j我們可以寫一個(gè)測(cè)試用例:
但是很多時(shí)候我們希望能夠傳遞參數(shù),通過(guò)閱讀上面的源碼可以得知:
if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") { // 接收到一個(gè)實(shí)現(xiàn)handleEvent接口的EventListener對(duì)象 handleEvent = eventListenerOrFunction.handleEvent; eventListener = eventListenerOrFunction; } else { // 接收到處理事件的回調(diào)函數(shù) handleEvent = eventListenerOrFunction; }我們上面的例子中我們傳遞的是一個(gè)方法,這里我們也可以傳遞一個(gè)EventListener對(duì)象。
// 創(chuàng)建事件通道 channel.create("onTest"); // 訂閱事件 channel.onTest.subscribe(function (event) { console.log(event); console.log(event.data.name+" fire"); }); // 創(chuàng)建 Event 對(duì)象 var event = document.createEvent("Events"); // 初始化事件 event.initEvent("onTest", false, false); // 綁定數(shù)據(jù) event.data = {name: "test"}; // 觸發(fā)事件 channel.onTest.fire(event);工具模塊我們?cè)趯懖寮臅r(shí)候如果熟悉cordova自帶的工具函數(shù),可以更加方便的拓展自己的插件。
define("cordova/utils", function(require, exports, module) { var utils = exports; // 定義對(duì)象屬性(或方法)的setter/getter utils.defineGetterSetter = function(obj, key, getFunc, opt_setFunc) {...} // 定義對(duì)象屬性(或方法)的getter utils.defineGetter = utils.defineGetterSetter; // Array IndexOf 方法 utils.arrayIndexOf = function(a, item) {...} // Array remove 方法 utils.arrayRemove = function(a, item) {...} // 類型判斷 utils.typeName = function(val) {...} // 數(shù)組判斷 utils.isArray = Array.isArray || function(a) {return utils.typeName(a) == "Array";}; // Date判斷 utils.isDate = function(d) {...} // 深度拷貝 utils.clone = function(obj) {...} // 函數(shù)包裝調(diào)用 utils.close = function(context, func, params) {...} // 內(nèi)部私有函數(shù),產(chǎn)生隨機(jī)數(shù) function UUIDcreatePart(length) {...} // 創(chuàng)建 UUID (通用唯一識(shí)別碼) utils.createUUID = function() {...} // 繼承 utils.extend = function() {...} // 調(diào)試 utils.alert = function(msg) {...} });UUIDcreatePart函數(shù)用來(lái)隨機(jī)產(chǎn)生一個(gè)16進(jìn)制的號(hào)碼,接受一個(gè)表示號(hào)碼長(zhǎng)度的參數(shù)(實(shí)際上是最終號(hào)碼長(zhǎng)度的一半),一般用途是做為元素的唯一ID。
utils.isArray 在這里不使用instanceof來(lái)判斷是不是Array類型,主要是考慮到跨域或者多個(gè)frame的情況,多個(gè)frame時(shí)每個(gè)frame都會(huì)有自己的Array構(gòu)造函數(shù),從而得出不正確的結(jié)論。使用"[object Array]"來(lái)判斷是根據(jù)ECMA標(biāo)準(zhǔn)中的返回值來(lái)進(jìn)行的,事實(shí)上,這里不需要類型轉(zhuǎn)換,而可以用全等“===”來(lái)判斷。
utils.close函數(shù),封裝函數(shù)的調(diào)用,將執(zhí)行環(huán)境作為一個(gè)參數(shù),調(diào)用的函數(shù)為第二個(gè)參數(shù),調(diào)用函數(shù)本身的參數(shù)為后續(xù)參數(shù)。
原型繼承實(shí)現(xiàn)詳解
utils.extend = (function() { // proxy used to establish prototype chain var F = function() {}; // extend Child from Parent return function(Child, Parent) { F.prototype = Parent.prototype; Child.prototype = new F(); Child.__super__ = Parent.prototype; Child.prototype.constructor = Child; }; }());這里的繼承是通過(guò)原型鏈的方式實(shí)現(xiàn),我們可以通過(guò)下述方式調(diào)用:
var Parent = function () { this.name = "Parent"; } Parent.prototype.getName = function () { return this.name; } var Child = function () { this.name = "Child"; } utils.extend(Child, Parent); var child = new Child(); console.log(child.getName())ES5中有一個(gè)Object.create方法,我們可以使用這個(gè)函數(shù)實(shí)現(xiàn)繼承:
// 創(chuàng)建一個(gè)新的對(duì)象 Object.create = Object.create || function (proto) { var F = function () {}; F.prototype = proto; return new F(); } // 實(shí)現(xiàn)繼承 var extend = function(Child, Parent) { // 拷貝Parent原型對(duì)象 Child.prototype = Object.create(Parent.prototype); // 將Child構(gòu)造函數(shù)賦值給Child的原型對(duì)象 Child.prototype.constructor = Child; } // 實(shí)例 var Parent = function () { this.name = "Parent"; } Parent.prototype.getName = function () { return this.name; } var Child = function () { this.name = "Child"; } extend(Child, Parent); var child = new Child(); console.log(child.getName())原型鏈的概念對(duì)于初學(xué)者而言可能有點(diǎn)繞,但是我們把握構(gòu)造函數(shù)、實(shí)例化對(duì)象、原型對(duì)象三者的關(guān)系就很簡(jiǎn)單了。我們以此為例說(shuō)明:
// 構(gòu)造函數(shù) var Child = function () { this.name = "Child"; } // 原型對(duì)象Child.prototype Child.prototype.getName = function () { return this.name; } // 實(shí)例化對(duì)象 var child = new Child();原型對(duì)象是構(gòu)造函數(shù)的prototype屬性,是所有實(shí)例化對(duì)象共享屬性和方法的原型對(duì)象。
實(shí)例化對(duì)象通過(guò)new構(gòu)造函數(shù)得到,都繼承了原型對(duì)象的屬性和方法。
如何訪(qiu)問(wèn)(jie)原型對(duì)象?若已知構(gòu)造函數(shù)Child,則可以通過(guò)Child.prototype得到;若已知實(shí)例化對(duì)象child,則可以通過(guò)child.__proto__或者Object.getPrototypeOf(child)得到,也通過(guò)Object.setPrototypeOf方法來(lái)重寫對(duì)象的原型。
Child.prototype === child.__proto__ // true child.__proto__ === Object.getPrototypeOf(child) // true原型對(duì)象中有個(gè)隱式的constructor,指向了構(gòu)造函數(shù)本身,也就是我們可以通過(guò)Child.prototype.constructor(雖然看似多此一舉,但是經(jīng)常需要重新設(shè)置構(gòu)造函數(shù))或child.__proto__.constructor或者Object.getPrototypeOf(child).constructor得到構(gòu)造函數(shù)。
instanceof和Object.isPrototypeOf()可以判斷兩個(gè)對(duì)象是否是繼承關(guān)系
child instanceof Child // true Child.prototype.isPrototypeOf(child) // true至此構(gòu)造函數(shù)、實(shí)例化對(duì)象、原型對(duì)象三者的關(guān)系我們已經(jīng)很清除了,再回過(guò)頭看看上面繼承的實(shí)現(xiàn)就很簡(jiǎn)單了。
我們可以通過(guò)instanceof來(lái)檢驗(yàn)是否滿足繼承關(guān)系:
child instanceof Child && child instanceof Parent // true其實(shí)上述繼承的思路很簡(jiǎn)單:
1.首先獲得父類原型對(duì)象的方法,這里的F對(duì)象作為中間變量引用拷貝Parent.prototype對(duì)象(即和Parent.prototype共享同一內(nèi)存空間);例如我們修改上述的Object.create為:Object.create = function (proto) { var F = function () {}; F.prototype = proto; F.prototype.setName = function(name){ this.name = name; } return new F(); }此時(shí)Parent.prototype、Child.prototype、child都擁有的setName方法,但是我們應(yīng)當(dāng)避免這樣做,這也是為什么我們不直接通過(guò)Child.prototype = Parent.prototype獲得;通過(guò)實(shí)例化中間對(duì)象F間接得到Parent.prototype的方法,此時(shí)通過(guò)Object.create方法獲得的對(duì)象和Parent.prototype不再是共享內(nèi)存空間。Child通過(guò)extend(Child, Parent)從Parent.prototype對(duì)象獲得一份新的拷貝。實(shí)質(zhì)是因?yàn)槲覀兺ㄟ^(guò)new一個(gè)構(gòu)造函數(shù)獲得的實(shí)例化對(duì)象是獲得了一個(gè)新的內(nèi)存空間,子對(duì)象互不影響;
2.對(duì)子類進(jìn)行修正,我們通過(guò)拷貝獲得了父類的一個(gè)備份,此時(shí)子類原型對(duì)象下的constructor屬性依然是父類的構(gòu)造函數(shù),顯然不符合我們的要求,我們需要重置,同時(shí)有時(shí)候我們希望保留對(duì)父類的引用,如cordova這里用一個(gè)__super__屬性保存。Child.__super__ = Parent.prototype; Child.prototype.constructor = Child;其實(shí)繼承的本質(zhì)我們是希望能實(shí)現(xiàn)以下功能:
父類有的我都有,我也能重載,但不至于影響到父類的屬性和方法
除了繼承之外,我也能添加自己的方法和屬性
我們可以利用es6新特性實(shí)現(xiàn)同樣的效果:
class Parent { constructor () { this.name = "Parent"; } getName () { return this.name; } } class Child extends Parent { constructor () { super(); this.name = "Child"; } } var child = new Child(); console.log(child.getName())super關(guān)鍵字在這里表示父類的構(gòu)造函數(shù),用來(lái)新建父類的this對(duì)象。在子類的構(gòu)造函數(shù)中,只有調(diào)用super之后,才可以使用this關(guān)鍵字,否則會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇悓?shí)例的構(gòu)建,是基于對(duì)父類實(shí)例加工,只有super方法才能返回父類實(shí)例。
cordova 模塊本文最后一部分我們來(lái)看看cordova模塊,cordova模塊是事件的處理和回調(diào),外部訪問(wèn)cordova.js的入口。
define("cordova", function(require, exports, module) { if (window.cordova && !(window.cordova instanceof HTMLElement)) { throw new Error("cordova already defined"); } // 導(dǎo)入事件通道模塊 var channel = require("cordova/channel"); // 導(dǎo)入平臺(tái)模塊 var platform = require("cordova/platform"); // 保存addEventListener、removeEventListener的原生實(shí)現(xiàn) var m_document_addEventListener = document.addEventListener; var m_document_removeEventListener = document.removeEventListener; var m_window_addEventListener = window.addEventListener; var m_window_removeEventListener = window.removeEventListener; // 緩存所有的事件處理函數(shù) var documentEventHandlers = {}, windowEventHandlers = {}; // 重新定義addEventListener、removeEventListener,方便后面注冊(cè)添加pause、resume、deviceReady等事件 document.addEventListener = function(evt, handler, capture) {...} window.addEventListener = function(evt, handler, capture) {...} document.removeEventListener = function(evt, handler, capture) {...} window.removeEventListener = function(evt, handler, capture) {...} function createEvent(type, data) {...} var cordova = { define: define, require: require, version: PLATFORM_VERSION_BUILD_LABEL, platformVersion: PLATFORM_VERSION_BUILD_LABEL, platformId: platform.id, addWindowEventHandler: function(event) {...}, addStickyDocumentEventHandler: function(event) {...}, addDocumentEventHandler: function(event) {...}, removeWindowEventHandler: function(event) {...}, removeDocumentEventHandler: function(event) {...}, getOriginalHandlers: function() {...}, fireDocumentEvent: function(type, data, bNoDetach) {...}, fireWindowEvent: function(type, data) {...}, callbackId: Math.floor(Math.random() * 2000000000), callbacks: {}, callbackStatus: {}, callbackSuccess: function(callbackId, args) {...}, callbackError: function(callbackId, args) {...}, callbackFromNative: function(callbackId, isSuccess, status, args, keepCallback) {...}, addConstructor: function(func) {...} } // 暴露cordova對(duì)象給外部 module.exports = cordova; });這里我們以document Event為例說(shuō)明一下cordova模塊中關(guān)于事件的處理:
// 保存addEventListener、removeEventListener的原生實(shí)現(xiàn) var m_document_addEventListener = document.addEventListener; var m_document_removeEventListener = document.removeEventListener; // 緩存事件處理函數(shù) var documentEventHandlers = {}; // 重新定義addEventListener document.addEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); if (typeof documentEventHandlers[e] != "undefined") { documentEventHandlers[e].subscribe(handler); } else { m_document_addEventListener.call(document, evt, handler, capture); } }; // 重新定義removeEventListener document.removeEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); // If unsubscribing from an event that is handled by a plugin if (typeof documentEventHandlers[e] != "undefined") { documentEventHandlers[e].unsubscribe(handler); } else { m_document_removeEventListener.call(document, evt, handler, capture); } }; // 創(chuàng)建 Event 對(duì)象 function createEvent(type, data) { var event = document.createEvent("Events"); event.initEvent(type, false, false); if (data) { for (var i in data) { if (data.hasOwnProperty(i)) { event[i] = data[i]; } } } return event; } var codova = { ... // 創(chuàng)建事件通道 addStickyDocumentEventHandler:function(event) { return (documentEventHandlers[event] = channel.createSticky(event)); }, addDocumentEventHandler:function(event) { return (documentEventHandlers[event] = channel.create(event)); }, // 取消事件通道 removeDocumentEventHandler:function(event) { delete documentEventHandlers[event]; }, // 發(fā)布事件消息 fireDocumentEvent: function(type, data, bNoDetach) { var evt = createEvent(type, data); if (typeof documentEventHandlers[type] != "undefined") { if( bNoDetach ) { documentEventHandlers[type].fire(evt); } else { setTimeout(function() { // Fire deviceready on listeners that were registered before cordova.js was loaded. if (type == "deviceready") { document.dispatchEvent(evt); } documentEventHandlers[type].fire(evt); }, 0); } } else { document.dispatchEvent(evt); } }, ... } module.exports = cordova;在初始化啟動(dòng)模塊cordova/init中有這樣的代碼:
// 注冊(cè)pause、resume、deviceReady事件 channel.onPause = cordova.addDocumentEventHandler("pause"); channel.onResume = cordova.addDocumentEventHandler("resume"); channel.onActivated = cordova.addDocumentEventHandler("activated"); channel.onDeviceReady = cordova.addStickyDocumentEventHandler("deviceready"); // 監(jiān)聽(tīng)DOMContentLoaded事件并發(fā)布事件消息 if (document.readyState == "complete" || document.readyState == "interactive") { channel.onDOMContentLoaded.fire(); } else { document.addEventListener("DOMContentLoaded", function() { channel.onDOMContentLoaded.fire(); }, false); } // 原生層加載完成事件 if (window._nativeReady) { channel.onNativeReady.fire(); } // 加載完成發(fā)布時(shí)間事件消息 channel.join(function() { modulemapper.mapModules(window); platform.initialize && platform.initialize(); channel.onCordovaReady.fire(); channel.join(function() { require("cordova").fireDocumentEvent("deviceready"); }, channel.deviceReadyChannelsArray); }, platformInitChannelsArray);這里通過(guò)addDocumentEventHandler及addStickyDocumentEventHandler創(chuàng)建了事件通道,并通過(guò)fireDocumentEvent或者fire發(fā)布事件消息,這樣我們就可以通過(guò)document.addEventListener訂閱監(jiān)聽(tīng)事件了。
如果我們要?jiǎng)?chuàng)建一個(gè)自定義事件Test,我們可以這樣做:
// 創(chuàng)建事件通道 cordova.addWindowEventHandler("Test"); // 發(fā)布事件消息 cordova.fireWindowEvent("Test", { name: "test", data: { time: new Date() } }) // 訂閱事件消息 window.addEventListener("Test", function (evt) { console.log(evt); });參考Cordova 3.x 入門 - 目錄
寫在后面
PhoneGap源碼分析本文至此已經(jīng)說(shuō)完了cordova的模塊機(jī)制和事件機(jī)制,已經(jīng)cordova的工具模塊,了解這些后寫起插件來(lái)才能得心應(yīng)手,對(duì)于原理實(shí)現(xiàn)部分不屬于本文的范疇,下一篇會(huì)詳細(xì)講解cordova原理實(shí)現(xiàn)。敬請(qǐng)關(guān)注,不過(guò)近來(lái)在寫畢設(shè),估計(jì)一時(shí)半會(huì)兒也不會(huì)寫完,本文前前后后已是拖了半個(gè)月。如果覺(jué)得本文對(duì)您有幫助,不妨打賞支持以此鼓勵(lì)。
轉(zhuǎn)載需標(biāo)注本文原始地址:https://zhaomenghuan.github.io/
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/82904.html
摘要:任何初始化任務(wù)應(yīng)該在文件中的事件的事件處理函數(shù)中。這個(gè)配置文件有幾個(gè)地方很關(guān)鍵,一開(kāi)始沒(méi)有認(rèn)真看,將插件導(dǎo)進(jìn)工程跑的時(shí)候各種問(wèn)題,十分頭痛,不得不重新認(rèn)真看看文檔。 前言 來(lái)新公司的第一個(gè)任務(wù),研究hybrid App中間層實(shí)現(xiàn)原理,做中間層插件開(kāi)發(fā)。這個(gè)任務(wù)挺有意思,也很有挑戰(zhàn)性,之前在DCloud雖然做過(guò)5+ App開(kāi)發(fā),但是中間層的東西確實(shí)涉及不多。本系列文章屬于系列開(kāi)篇cord...
摘要:之前做過(guò)一點(diǎn)前端的小項(xiàng)目所以前端還算熟練因?yàn)樽罱跍?zhǔn)備所以想能不能寫一個(gè)背單詞軟件正好這學(xué)期有個(gè)開(kāi)發(fā)課,就用來(lái)當(dāng)大作業(yè)了前端后端如何在下調(diào)試當(dāng)然是代理啦在之前兩個(gè)項(xiàng)目中為了不用代理強(qiáng)行在后端啟用了事實(shí)證明這是個(gè)愚蠢的決定因?yàn)橥耆贿m合做后端 之前做過(guò)一點(diǎn)前端的小項(xiàng)目所以前端還算熟練因?yàn)樽罱跍?zhǔn)備GRE所以想能不能寫一個(gè)背單詞軟件正好這學(xué)期有個(gè)Android開(kāi)發(fā)課,就用來(lái)當(dāng)大作業(yè)了 前端...
摘要:第期新聞發(fā)布是熱門的通用服務(wù)器端的延伸框架,近日發(fā)布了版本。官方博客相關(guān)報(bào)導(dǎo)官網(wǎng)全新改版工具的官網(wǎng)近日全新改版上線,文檔與首頁(yè)都有全新的版面與改善。官方網(wǎng)站研習(xí)會(huì)報(bào)導(dǎo)是在月底,于歐洲的社群的一個(gè)研習(xí)會(huì)活動(dòng)。 第022期 (2017.04.02) 新聞 Next.js發(fā)布2.0 Next.js是熱門的通用(服務(wù)器端)的React延伸框架,近日發(fā)布了2.0版本。2.0的目標(biāo)有三個(gè),是針對(duì)...
閱讀 887·2023-04-26 00:37
閱讀 749·2021-11-24 09:39
閱讀 2181·2021-11-23 09:51
閱讀 3877·2021-11-22 15:24
閱讀 767·2021-10-19 11:46
閱讀 1890·2019-08-30 13:53
閱讀 2455·2019-08-29 17:28
閱讀 1342·2019-08-29 14:11