cordova(PhoneGap) 是一個優秀的經典的中間件框架,網上對其源代碼解讀的文章確實很少,本系列文章試着解讀一下,以便對cordova 框架的原理理解得更深刻。本文源碼爲cordova android版本6.1.2
。javascript
咱們使用IDE的代碼摺疊功能先從總體上把握代碼結構。html
/* * 版權申明及註釋部分 */ ;(function() { ... })();
;
是保證導入的其它js腳本,使用工具壓縮js文件時不出錯。一個自執行匿名函數包裹,防止內部變量污染到外部命名空間。閱讀過jQuery源碼的人都知道,jQuery的也是相同的結構,只是jQuery定義的匿名函數多了兩個參數window和undefined,而後調用的時候只傳入window,這樣,window能夠在jQuery內部安全使用,而undefined也的確表示未定義(有些瀏覽器實現容許重定義undefined)。java
繼續展開代碼,能夠看到以下的結構:node
;(function() { var PLATFORM_VERSION_BUILD_LABEL = '6.1.2'; // 模塊化系統 /* ------------------------------------------------------------- */ var require, // 加載使用module define; // 定義註冊module // require|define 的邏輯 (function () { ... })(); // Export for use in node if (typeof module === "object" && typeof require === "function") { module.exports.require = require; module.exports.define = define; } /* ------------------------------------------------------------- */ // 事件的處理和回調,外部訪問cordova.js的入口 define("cordova", function(require, exports, module) { ... } // JS->Native的具體交互形式 define("cordova/android/nativeapiprovider", function(require, exports, module) { ... } // 經過prompt()和Native交互 define("cordova/android/promptbasednativeapi", function(require, exports, module) { ... } // 用於plugin中校驗參數,好比argscheck.checkArgs('fFO', 'Camera.getPicture', arguments); 參數應該是2個函數1個對象 define("cordova/argscheck", function(require, exports, module) { ... } // JS->Native交互時對ArrayBuffer進行uint8ToBase64(WebSockets二進制流) define("cordova/base64", function(require, exports, module) { ... } // 對象屬性操做,好比把一個對象的屬性Merge到另一個對象 define("cordova/builder", function(require, exports, module) { ... } // 事件通道 define("cordova/channel", function(require, exports, module) { ... } // 執行JS->Native交互 define("cordova/exec", function(require, exports, module) { ... } // 用於Plugin中往已經有的模塊上添加方法 define("cordova/exec/proxy", function(require, exports, module) { ... } // 初始化處理 define("cordova/init", function(require, exports, module) { ... } define("cordova/init_b", function(require, exports, module) { ... } // 把定義的模塊clobber到一個對象,在初始化的時候會賦給window define("cordova/modulemapper", function(require, exports, module) { ... } define("cordova/modulemapper_b", function(require, exports, module) { ... } // 平臺啓動處理 define("cordova/platform", function(require, exports, module) { ... } // 清緩存、loadUrl、退出程序等 define("cordova/plugin/android/app", function(require, exports, module) { ... } // 載全部cordova_plugins.js中定義的模塊,執行完成後會觸發 define("cordova/pluginloader", function(require, exports, module) { ... } define("cordova/pluginloader_b", function(require, exports, module) { ... } // 獲取絕對URL,InAppBrowser中會用到 define("cordova/urlutil", function(require, exports, module) { ... } // 工具類 define("cordova/utils", function(require, exports, module) { ... } // 全部模塊註冊完以後,導入cordova至全局環境中 window.cordova = require('cordova'); // 初始化啓動 require('cordova/init'); })();
從上能夠清晰的看出,在cordova內部,首先是定義了兩個公共的require和define函數,而後是使用define註冊全部模塊,再經過window.cordova=require('cordova')導入庫文件至全局執行環境中。android
相似於Java的package/import,在JavaScript中也有相似的define/require,它用來異步加載module化的js,從而提升運行效率。模塊化加載的必要性,起源於nodejs的出現。可是JavaScript並無內置模塊系統,因此就出現了不少規範。 主要有2種:CommonJS 和 AMD(Asynchronous Module Definition)。還有國內興起的CMD(Common Module Definition) 。CommonJS主要面對的是服務器,表明是Node.js;AMD針對瀏覽器進行了優化,主要實現require.js;CMD是seajs。 git
cordova-js最開始採用的是require.js做者寫的almond.js(兼容AMD和CommonJS),但以後因爲特殊需求(好比模塊不存在的時候要throw異常),最終從almond.js fork過來實現了一個簡易CommonJS風格的模塊系統,同時提供了和nodejs之間很好的交互。在cordova.js中能夠直接使用define()和require(),在其餘文件能夠經過cordova.define()和cordova.require()來調用。因此src/scripts/require.js中定義的就是一個精簡的JavaScript模塊系統。 es6
// cordova.js內部使用的全局函數require/define var require, define; (function () { // 初始化一個空對象,緩存全部的模塊 var modules = {}, // 正在build中的模塊ID的棧 requireStack = [], // 標示正在build中模塊ID的Map inProgressModules = {}, SEPARATOR = "."; // 模塊build function build(module) { // 備份工廠方法 var factory = module.factory, // 對require對象進行特殊處理 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); }; // 給模塊定義一個空的exports對象,防止工廠類方法中的空引用 module.exports = {}; // 刪除工廠方法 delete module.factory; // 調用備份的工廠方法(參數必須是require,exports,module) factory(localRequire, module.exports, module); // 返回工廠方法中實現的module.exports對象 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; } // 若是模塊存在工廠方法說明還未進行build(require嵌套) if (modules[id].factory) { try { // 標示該模塊正在build inProgressModules[id] = requireStack.length; // 將該模塊壓入請求棧 requireStack.push(id); // 模塊build,成功後返回module.exports return build(modules[id]); } finally { // build完成後刪除當前請求 delete inProgressModules[id]; requireStack.pop(); } } // build完的模塊直接返回module.exports return modules[id].exports; }; // 定義註冊模塊 define = function (id, factory) { // 若是已經存在拋出異常 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環境中定義require和define兩個變量,用來存儲實現導入功能的函數和實現註冊功能的函數。而後用一個當即調用的匿名函數來實例化這兩個變量,在這個匿名函數內部,緩存了全部的功能模塊。註冊模塊時,若是已經註冊了,就直接拋出異常,防止無心中重定義,如確實須要重定義,可先調用define.remove。github
從內部私有函數build中,能夠看出,調用工廠函數時, factory(localRequire, module.exports, module);
第一個參數localRequire
實質仍是調用全局的require()
函數,只是把ID稍微加工了一下支持相對路徑。cordova.js沒有用到相對路徑的require,但在一些Plugin的js中有,好比Contact.js
中 ContactError = require('./ContactError');
api
這裏咱們寫個測試用例:跨域
<script src="module.js"></script> <script type="text/javascript"> define('plugin.first', function (require, exports, module) { module.exports = { name: 'first plugin', show: function () { console.log("call "+this.name); } } }); define('plugin.second', function (require, exports, module) { var first = require("plugin.first"); first.show(); }); require("plugin.second"); // call first plugin </script>
注:module.js爲上述cordova的模塊代碼。
上面例子中咱們定義了兩個模塊,這裏是寫在同一個頁面下,在實際中咱們天然但願寫在兩個不一樣的文件中,而後按需加載。咱們上一篇文章中說明了cordova的插件使用方法,咱們會發現cordova_plugins.js
中定義了cordova插件的id、路徑等變量,而且該文件定義了一個id爲cordova/plugin_list
的模塊,咱們在cordova.js中能夠看到有這個模塊的引用。
定義了require和define並賦值後,是將cordova全部模塊一一註冊,例如:
define("cordova",function(require,exports,module){ // 工廠函數內部實現代碼 });
這裏須要注意的是,define只是註冊模塊,不會調用其factory。factory函數在這個時候並無實際執行,而只是定義,並做爲一個參數傳遞給define函數。全部模塊註冊完以後,經過:
window.cordova = require('cordova');
導入至全局環境。
由於是註冊後第一次導入,因此在執行require('cordova')
時,modules['cordova'].factory
的值是註冊時的工廠函數,轉變爲boolean值時爲true,從而在這裏會經過build調用這個工廠函數,並將這個工廠函數從註冊緩存裏面刪除,接下來的就是去執行cordova的這個factory函數了。
做爲觀察者模式(Observer)的一種變形,不少MV*框架(好比:Vue.js、Backbone.js)中都提供發佈/訂閱模型來對代碼進行解耦。cordova.js中也提供了一個自定義的pub-sub模型,基於該模型提供了一些事件通道,用來控制通道中的事件何時以什麼樣的順序被調用,以及各個事件通道的調用。
src/common/channel.js的代碼結構也是一個很經典的定義結構(構造函數、實例、修改函數原型共享實例方法),它提供事件通道上事件的訂閱(subscribe)、撤消訂閱(unsubscribe)、調用(fire)。pub-sub模型用於定義和控制對cordova初始化的事件的觸發以及此後的自定義事件。
頁面加載和Cordova啓動期間的事件順序以下:
能夠經過下面的事件進行監聽:
document.addEventListener("deviceready", myDeviceReadyListener, false); document.addEventListener("resume", myResumeListener, false); document.addEventListener("pause", myPauseListener, false);
DOM生命週期事件應用於保存和恢復狀態:
define("cordova/channel", function(require, exports, module) { var utils = require('cordova/utils'), nextGuid = 1; // 事件通道的構造函數 var Channel = function(type, sticky) { // 通道名稱 this.type = type; // 通道上的全部事件處理函數Map(索引爲guid) this.handlers = {}; // 通道的狀態(0:非sticky, 1:sticky但未調用, 2:sticky已調用) this.state = sticky ? 1 : 0; // 對於sticky事件通道備份傳給fire()的參數 this.fireArgs = null; // 當前通道上的事件處理函數的個數 this.numHandlers = 0; // 訂閱第一個事件或者取消訂閱最後一個事件時調用自定義的處理 this.onHasSubscribersChange = null; }, // 事件通道外部接口 channel = { // 把指定的函數h訂閱到c的各個通道上,保證h在每一個通道的最後被執行 join: function(h, c) { var len = c.length, i = len, f = function() { if (!(--i)) h(); }; for (var j=0; j<len; j++) { if (c[j].state === 0) { throw Error('Can only use join with sticky channels.'); } c[j].subscribe(f); } if (!len) h(); }, // 建立事件通道 create: function(type) { return channel[type] = new Channel(type, false); }, // 建立sticky事件通道 createSticky: function(type) { return channel[type] = new Channel(type, true); }, // 保存deviceready事件以前要調用的事件 deviceReadyChannelsArray: [], deviceReadyChannelsMap: {}, // 設置deviceready事件以前必需要完成的事件 waitForInitialization: function(feature) { if (feature) { var c = channel[feature] || this.createSticky(feature); this.deviceReadyChannelsMap[feature] = c; this.deviceReadyChannelsArray.push(c); } }, // 初始化代碼已經完成 initializationComplete: function(feature) { var c = this.deviceReadyChannelsMap[feature]; if (c) { c.fire(); } } }; // 校驗事件處理函數 function checkSubscriptionArgument(argument) { if (typeof argument !== "function" && typeof argument.handleEvent !== "function") { throw new Error( "Must provide a function or an EventListener object " + "implementing the handleEvent interface." ); } } /** * 向事件通道訂閱事件處理函數(subscribe部分) * f:事件處理函數 c:事件的上下文 */ Channel.prototype.subscribe = function(eventListenerOrFunction, eventListener) { // 校驗事件處理函數 checkSubscriptionArgument(eventListenerOrFunction); var handleEvent, guid; if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") { // 接收到一個實現handleEvent接口的EventListener對象 handleEvent = eventListenerOrFunction.handleEvent; eventListener = eventListenerOrFunction; } else { // 接收處處理事件的回調函數 handleEvent = eventListenerOrFunction; } // 若是是被訂閱過的sticky事件,就直接調用 if (this.state == 2) { handleEvent.apply(eventListener || this, this.fireArgs); return; } guid = eventListenerOrFunction.observer_guid; // 若是事件有上下文,要先把事件函數包裝一下帶上上下文 if (typeof eventListener === "object") { handleEvent = utils.close(eventListener, handleEvent); } // 自增加的ID if (!guid) { guid = '' + nextGuid++; } // 把自增加的ID反向設置給函數,之後撤消訂閱或內部查找用 handleEvent.observer_guid = guid; eventListenerOrFunction.observer_guid = guid; // 判斷該guid索引的事件處理函數是否存在(保證訂閱一次) if (!this.handlers[guid]) { // 訂閱到該通道上(索引爲guid) this.handlers[guid] = handleEvent; // 通道上的事件處理函數的個數增1 this.numHandlers++; if (this.numHandlers == 1) { // 訂閱第一個事件時調用自定義的處理(好比:第一次按下返回按鈕提示「再按一次...」) this.onHasSubscribersChange && this.onHasSubscribersChange(); } } }; /** * 撤消訂閱通道上的某個函數(guid) */ Channel.prototype.unsubscribe = function(eventListenerOrFunction) { // 事件處理函數校驗 checkSubscriptionArgument(eventListenerOrFunction); var handleEvent, guid, handler; if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") { // 接收到一個實現handleEvent接口的EventListener對象 handleEvent = eventListenerOrFunction.handleEvent; } else { // 接收處處理事件的回調函數 handleEvent = eventListenerOrFunction; } // 事件處理函數的guid索引 guid = handleEvent.observer_guid; // 事件處理函數 handler = this.handlers[guid]; if (handler) { // 從該通道上撤消訂閱(索引爲guid) delete this.handlers[guid]; // 通道上的事件處理函數的個數減1 this.numHandlers--; if (this.numHandlers === 0) { // 撤消訂閱最後一個事件時調用自定義的處理 this.onHasSubscribersChange && this.onHasSubscribersChange(); } } }; /** * 調用全部被髮布到該通道上的函數 */ Channel.prototype.fire = function(e) { var fail = false, fireArgs = Array.prototype.slice.call(arguments); // sticky事件被調用時,標示爲已經調用過 if (this.state == 1) { this.state = 2; this.fireArgs = fireArgs; } if (this.numHandlers) { // 把該通道上的全部事件處理函數拿出來放到一個數組中 var toCall = []; for (var item in this.handlers) { toCall.push(this.handlers[item]); } // 依次調用通道上的全部事件處理函數 for (var i = 0; i < toCall.length; ++i) { toCall[i].apply(this, fireArgs); } // sticky事件是一次性所有被調用的,調用完成後就清空 if (this.state == 2 && this.numHandlers) { this.numHandlers = 0; this.handlers = {}; this.onHasSubscribersChange && this.onHasSubscribersChange(); } } }; /** * 建立事件通道(publish部分) */ //(內部事件通道)頁面加載後DOM解析完成 channel.createSticky('onDOMContentLoaded'); //(內部事件通道)Cordova的native準備完成 channel.createSticky('onNativeReady'); //(內部事件通道)全部Cordova的JavaScript對象被建立完成能夠開始加載插件 channel.createSticky('onCordovaReady'); //(內部事件通道)全部自動load的插件js已經被加載完成(待刪除) channel.createSticky('onPluginsReady'); // Cordova所有準備完成 channel.createSticky('onDeviceReady'); // 應用從新返回前臺 channel.create('onResume'); // 應用暫停退到後臺 channel.create('onPause'); // 設置deviceready事件以前必需要完成的事件 channel.waitForInitialization('onCordovaReady'); channel.waitForInitialization('onDOMContentLoaded'); module.exports = channel; });
咱們能夠寫一個測試用例:
<script src="channel.js"></script> <script type="text/javascript"> var test = channel.create('onTest'); // 訂閱事件(此處test = channel.onTest) test.subscribe(function () { console.log('test fire'); }); // 觸發事件(此處test = channel.onTest) test.fire(); </script>
可是不少時候咱們但願可以傳遞參數,經過閱讀上面的源碼能夠得知:
if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") { // 接收到一個實現handleEvent接口的EventListener對象 handleEvent = eventListenerOrFunction.handleEvent; eventListener = eventListenerOrFunction; } else { // 接收處處理事件的回調函數 handleEvent = eventListenerOrFunction; }
咱們上面的例子中咱們傳遞的是一個方法,這裏咱們也能夠傳遞一個EventListener對象。
// 建立事件通道 channel.create('onTest'); // 訂閱事件 channel.onTest.subscribe(function (event) { console.log(event); console.log(event.data.name+' fire'); }); // 建立 Event 對象 var event = document.createEvent('Events'); // 初始化事件 event.initEvent('onTest', false, false); // 綁定數據 event.data = {name: 'test'}; // 觸發事件 channel.onTest.fire(event);
咱們在寫插件的時候若是熟悉cordova自帶的工具函數,能夠更加方便的拓展本身的插件。
define("cordova/utils", function(require, exports, module) { var utils = exports; // 定義對象屬性(或方法)的setter/getter utils.defineGetterSetter = function(obj, key, getFunc, opt_setFunc) {...} // 定義對象屬性(或方法)的getter utils.defineGetter = utils.defineGetterSetter; // Array IndexOf 方法 utils.arrayIndexOf = function(a, item) {...} // Array remove 方法 utils.arrayRemove = function(a, item) {...} // 類型判斷 utils.typeName = function(val) {...} // 數組判斷 utils.isArray = Array.isArray || function(a) {return utils.typeName(a) == 'Array';}; // Date判斷 utils.isDate = function(d) {...} // 深度拷貝 utils.clone = function(obj) {...} // 函數包裝調用 utils.close = function(context, func, params) {...} // 內部私有函數,產生隨機數 function UUIDcreatePart(length) {...} // 建立 UUID (通用惟一識別碼) utils.createUUID = function() {...} // 繼承 utils.extend = function() {...} // 調試 utils.alert = function(msg) {...} });
原型繼承實現詳解
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; }; }());
這裏的繼承是經過原型鏈的方式實現,咱們能夠經過下述方式調用:
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中有一個Object.create方法,咱們可使用這個函數實現繼承:
// 建立一個新的對象 Object.create = Object.create || function (proto) { var F = function () {}; F.prototype = proto; return new F(); } // 實現繼承 var extend = function(Child, Parent) { // 拷貝Parent原型對象 Child.prototype = Object.create(Parent.prototype); // 將Child構造函數賦值給Child的原型對象 Child.prototype.constructor = Child; } // 實例 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())
原型鏈的概念對於初學者而言可能有點繞,可是咱們把握構造函數、實例化對象、原型對象三者的關係就很簡單了。咱們以此爲例說明:
// 構造函數 var Child = function () { this.name = 'Child'; } // 原型對象Child.prototype Child.prototype.getName = function () { return this.name; } // 實例化對象 var child = new Child();
Child.prototype
獲得;若已知實例化對象child,則能夠經過child.__proto__
或者Object.getPrototypeOf(child)
獲得,也經過Object.setPrototypeOf方法來重寫對象的原型。Child.prototype === child.__proto__ // true child.__proto__ === Object.getPrototypeOf(child) // true
Child.prototype.constructor
(雖然看似畫蛇添足,可是常常須要從新設置構造函數)或child.__proto__.constructor
或者Object.getPrototypeOf(child).constructor
獲得構造函數。child instanceof Child // true Child.prototype.isPrototypeOf(child) // true
至此構造函數、實例化對象、原型對象三者的關係咱們已經很清除了,再回過頭看看上面繼承的實現就很簡單了。
咱們能夠經過instanceof
來檢驗是否知足繼承關係:
child instanceof Child && child instanceof Parent // true
其實上述繼承的思路很簡單:
1.首先得到父類原型對象的方法,這裏的F對象做爲中間變量引用拷貝Parent.prototype對象(即和Parent.prototype共享同一內存空間);例如咱們修改上述的Object.create爲:
Object.create = function (proto) { var F = function () {}; F.prototype = proto; F.prototype.setName = function(name){ this.name = name; } return new F(); }
此時Parent.prototype、Child.prototype、child都擁有的setName方法,可是咱們應當避免這樣作,這也是爲何咱們不直接經過Child.prototype = Parent.prototype
得到;經過實例化中間對象F間接獲得Parent.prototype的方法,此時經過Object.create方法得到的對象和Parent.prototype再也不是共享內存空間。Child經過extend(Child, Parent)
從Parent.prototype對象得到一份新的拷貝。實質是由於咱們經過new一個構造函數得到的實例化對象是得到了一個新的內存空間,子對象互不影響;
2.對子類進行修正,咱們經過拷貝得到了父類的一個備份,此時子類原型對象下的constructor屬性依然是父類的構造函數,顯然不符合咱們的要求,咱們須要重置,同時有時候咱們但願保留對父類的引用,如cordova這裏用一個__super__
屬性保存。
Child.__super__ = Parent.prototype; Child.prototype.constructor = Child;
其實繼承的本質咱們是但願能實現如下功能:
咱們能夠利用es6新特性實現一樣的效果:
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關鍵字在這裏表示父類的構造函數,用來新建父類的this對象。在子類的構造函數中,只有調用super以後,纔可使用this關鍵字,不然會報錯。這是由於子類實例的構建,是基於對父類實例加工,只有super方法才能返回父類實例。
本文最後一部分咱們來看看cordova模塊,cordova模塊是事件的處理和回調,外部訪問cordova.js的入口。
define("cordova", function(require, exports, module) { if (window.cordova && !(window.cordova instanceof HTMLElement)) { throw new Error("cordova already defined"); } // 導入事件通道模塊 var channel = require('cordova/channel'); // 導入平臺模塊 var platform = require('cordova/platform'); // 保存addEventListener、removeEventListener的原生實現 var m_document_addEventListener = document.addEventListener; var m_document_removeEventListener = document.removeEventListener; var m_window_addEventListener = window.addEventListener; var m_window_removeEventListener = window.removeEventListener; // 緩存全部的事件處理函數 var documentEventHandlers = {}, windowEventHandlers = {}; // 從新定義addEventListener、removeEventListener,方便後面註冊添加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對象給外部 module.exports = cordova; });
這裏咱們以document Event爲例說明一下cordova模塊中關於事件的處理:
// 保存addEventListener、removeEventListener的原生實現 var m_document_addEventListener = document.addEventListener; var m_document_removeEventListener = document.removeEventListener; // 緩存事件處理函數 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); } }; // 建立 Event 對象 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 = { ... // 建立事件通道 addStickyDocumentEventHandler:function(event) { return (documentEventHandlers[event] = channel.createSticky(event)); }, addDocumentEventHandler:function(event) { return (documentEventHandlers[event] = channel.create(event)); }, // 取消事件通道 removeDocumentEventHandler:function(event) { delete documentEventHandlers[event]; }, // 發佈事件消息 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;
在初始化啓動模塊cordova/init
中有這樣的代碼:
// 註冊pause、resume、deviceReady事件 channel.onPause = cordova.addDocumentEventHandler('pause'); channel.onResume = cordova.addDocumentEventHandler('resume'); channel.onActivated = cordova.addDocumentEventHandler('activated'); channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready'); // 監聽DOMContentLoaded事件併發布事件消息 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(); } // 加載完成發佈時間事件消息 channel.join(function() { modulemapper.mapModules(window); platform.initialize && platform.initialize(); channel.onCordovaReady.fire(); channel.join(function() { require('cordova').fireDocumentEvent('deviceready'); }, channel.deviceReadyChannelsArray); }, platformInitChannelsArray);
這裏經過addDocumentEventHandler及addStickyDocumentEventHandler建立了事件通道,並經過fireDocumentEvent或者fire發佈事件消息,這樣咱們就能夠經過document.addEventListener訂閱監聽事件了。
若是咱們要建立一個自定義事件Test,咱們能夠這樣作:
// 建立事件通道 cordova.addWindowEventHandler('Test'); // 發佈事件消息 cordova.fireWindowEvent('Test', { name: 'test', data: { time: new Date() } }) // 訂閱事件消息 window.addEventListener('Test', function (evt) { console.log(evt); });
Cordova 3.x 入門 - 目錄
PhoneGap源碼分析
本文至此已經說完了cordova的模塊機制和事件機制,已經cordova的工具模塊,瞭解這些後寫起插件來才能駕輕就熟,對於原理實現部分不屬於本文的範疇,下一篇會詳細講解cordova原理實現。敬請關注,不過近來在寫畢設,估計一時半會兒也不會寫完,本文前先後後已經是拖了半個月。若是以爲本文對您有幫助,不妨打賞支持以此鼓勵。
轉載需標註本文原始地址:https://zhaomenghuan.github.io/