延續(xù)上一篇 cordova的原理解釋, 參考這里
3. cordova PubSub模式 (channel)
cordova基于觀察者模式的,提供了一個(gè)自定義的pub-sub模型,基于該模型提供一些事件,用來控制事件什么時(shí)候以什么樣的順序被調(diào)用,以及事件的調(diào)用。
cordova/channel, 的代碼結(jié)構(gòu)也是一個(gè)很經(jīng)典的定義結(jié)構(gòu)(構(gòu)造函數(shù)、實(shí)例、修改函數(shù)原型共享實(shí)例方法),它提供事件通道的訂閱(subscribe)、撤消訂閱(unsubscribe)、調(diào)用(fire)等基本方法。最后發(fā)布了9個(gè)事件。
請(qǐng)看源碼 :
// 定義channel模塊
define("cordova/channel", function(require, exports, module) {
var utils = require('cordova/utils'),
nextGuid = 1;
/**
* Channel
* @constructor
* @param type String the channel name
* ①事件通道的構(gòu)造函數(shù)
*/
var Channel = function(type, sticky) {
// 事件通道名稱
this.type = type;
// 事件通道上的所有事件處理函數(shù)Map(索引為guid)
// Map of guid -> function.
this.handlers = {};
// 事件通道的狀態(tài)(0:非sticky, 1:sticky但未調(diào)用, 2:sticky已調(diào)用)
// 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired.
this.state = sticky ? 1 : 0;
// 傳給fire()的參數(shù)
// Used in sticky mode to remember args passed to fire().
this.fireArgs = null;
// 當(dāng)前通道上的事件處理函數(shù)的個(gè)數(shù)
// Used by onHasSubscribersChange to know if there are any listeners.
this.numHandlers = 0;
// 訂閱第一個(gè)事件或者取消訂閱最后一個(gè)事件時(shí)調(diào)用自定義的處理
// Function that is called when the first listener is subscribed, or when
// the last listener is unsubscribed.
this.onHasSubscribersChange = null;
},
// ②事件通道外部接口
channel = {
/**
* Calls the provided function only after all of the channels specified
* have been fired. All channels must be sticky channels.
*/
// 把指定的函數(shù)h訂閱到c的各個(gè)通道上,保證h在每個(gè)通道的最后被執(zhí)行
join: function(h, c) {
var len = c.length,
i = len,
f = function() {
if (!(--i)) h();
};
// 把事件處理函數(shù)h訂閱到c的各個(gè)事件通道上
for (var j=0; j<len; j++) {
// 必須是sticky事件通道
if (c[j].state === 0) {
throw Error('Can only use join with sticky channels.');
}
c[j].subscribe(f);
}
// 執(zhí)行h
if (!len) h();
},
// 創(chuàng)建事件通道
create: function(type) {
return channel[type] = new Channel(type, false);
},
// 創(chuàng)建sticky事件通道
createSticky: function(type) {
return channel[type] = new Channel(type, true);
},
/**
* cordova Channels that must fire before "deviceready" is fired.
*/
// 調(diào)用deviceready事件之前要調(diào)用的事件,
deviceReadyChannelsArray: [],
deviceReadyChannelsMap: {},
/**
* Indicate that a feature needs to be initialized before it is ready to be used.
* This holds up Cordova's "deviceready" event until the feature has been initialized
* and Cordova.initComplete(feature) is called.
*
* @param feature {String} The unique feature name
*/
// 設(shè)置deviceready事件之前必須要完成的事件
waitForInitialization: function(feature) {
if (feature) {
var c = channel[feature] || this.createSticky(feature);
this.deviceReadyChannelsMap[feature] = c;
this.deviceReadyChannelsArray.push(c);
}
},
/**
* Indicate that initialization code has completed and the feature is ready to be used.
* 表明代碼已經(jīng)初始化,并準(zhǔn)備好被使用
* @param feature {String} The unique feature name
*/
initializationComplete: function(feature) {
var c = this.deviceReadyChannelsMap[feature];
if (c) {
c.fire();
}
}
};
// 判斷是否函數(shù)類型, Js中類型的判斷可參考這里
function forceFunction(f) {
if (typeof f != 'function') throw "Function required as first argument!";
}
/**
* Subscribes the given function to the channel. Any time that
* Channel.fire is called so too will the function.
* Optionally specify an execution context for the function
* and a guid that can be used to stop subscribing to the channel.
* Returns the guid.
*/
// ③修改函數(shù)原型共享實(shí)例方法
// 向事件通道訂閱事件處理函數(shù)(subscribe部分)
// f:事件處理函數(shù) c:事件的上下文(可省略)
Channel.prototype.subscribe = function(f, c) {
// 判讀是否函數(shù)
forceFunction(f);
// 如果是被訂閱過的sticky事件,就直接調(diào)用。
if (this.state == 2) {
f.apply(c || this, this.fireArgs);
return;
}
var func = f,
guid = f.observer_guid;
// 如果事件有上下文,要先把事件函數(shù)包裝一下帶上上下文
if (typeof c == "object") { func = utils.close(c, f); }
// 自增長的ID
if (!guid) {
// first time any channel has seen this subscriber
guid = '' + nextGuid++;
}
// 把自增長的ID反向設(shè)置給函數(shù),以后撤消訂閱或內(nèi)部查找用
func.observer_guid = guid;
f.observer_guid = guid;
// 判斷該guid索引的事件處理函數(shù)是否存在(保證訂閱一次)
// Don't add the same handler more than once.
if (!this.handlers[guid]) {
// 訂閱到該通道上(索引為guid)
this.handlers[guid] = func;
// 通道上的事件處理函數(shù)的個(gè)數(shù)增加1
this.numHandlers++;
// 如果時(shí)間
if (this.numHandlers == 1) {
// 訂閱第一個(gè)事件時(shí)調(diào)用自定義的處理(比如:第一次按下返回按鈕提示“再按一次...”)
this.onHasSubscribersChange && this.onHasSubscribersChange();
}
}
};
/**
* Unsubscribes the function with the given guid from the channel.
*/
// 撤消訂閱通道上的某個(gè)函數(shù)(guid)
Channel.prototype.unsubscribe = function(f) {
// need a function to unsubscribe
// 事件處理函數(shù)校驗(yàn)
forceFunction(f);
// 事件處理函數(shù)的guid索引
var guid = f.observer_guid,
// 事件處理函數(shù)
handler = this.handlers[guid];
if (handler) {
// 從該通道上撤消訂閱(索引為guid)
delete this.handlers[guid];
// 通道上的事件處理函數(shù)的個(gè)數(shù)減1
this.numHandlers--;
if (this.numHandlers === 0) {
// 撤消訂閱最后一個(gè)事件時(shí)調(diào)用自定義的處理
this.onHasSubscribersChange && this.onHasSubscribersChange();
}
}
};
/**
* Calls all functions subscribed to this channel.
*/
// 調(diào)用所有被發(fā)布到該通道上的函數(shù)
Channel.prototype.fire = function(e) {
var fail = false,
fireArgs = Array.prototype.slice.call(arguments);
// Apply stickiness.
// sticky事件被調(diào)用時(shí),標(biāo)示為已經(jīng)調(diào)用過。
if (this.state == 1) {
this.state = 2;
this.fireArgs = fireArgs;
}
if (this.numHandlers) {
// 把該通道上的所有事件處理函數(shù)拿出來放到一個(gè)數(shù)組中。
var toCall = [];
for (var item in this.handlers) {
toCall.push(this.handlers[item]);
}
// 依次調(diào)用通道上的所有事件處理函數(shù)
for (var i = 0; i < toCall.length; ++i) {
toCall[i].apply(this, fireArgs);
}
// sticky事件是一次性全部被調(diào)用的,調(diào)用完成后就清空。
if (this.state == 2 && this.numHandlers) {
this.numHandlers = 0;
this.handlers = {};
this.onHasSubscribersChange && this.onHasSubscribersChange();
}
}
};
// ④創(chuàng)建事件通道(publish部分)-----------------------
// (內(nèi)部事件通道)頁面加載后DOM解析完成
channel.createSticky('onDOMContentLoaded');
// (內(nèi)部事件通道)Cordova的native準(zhǔn)備完成
channel.createSticky('onNativeReady');
// (內(nèi)部事件通道)所有Cordova的JavaScript對(duì)象被創(chuàng)建完成可以開始加載插件
channel.createSticky('onCordovaReady');
// (內(nèi)部事件通道)所有自動(dòng)load的插件js已經(jīng)被加載完成
channel.createSticky('onPluginsReady');
// Cordova全部準(zhǔn)備完成
channel.createSticky('onDeviceReady');
// 應(yīng)用重新返回前臺(tái)
channel.create('onResume');
// 應(yīng)用暫停退到后臺(tái)
channel.create('onPause');
// (內(nèi)部事件通道)應(yīng)用被關(guān)閉(window.onunload)
channel.createSticky('onDestroy');
// ⑤設(shè)置deviceready事件之前必須要完成的事件
// ***onNativeReady和onPluginsReady是平臺(tái)初期化之前要完成的。
channel.waitForInitialization('onCordovaReady');
channel.waitForInitialization('onDOMContentLoaded');
module.exports = channel;
});
以上是源碼,下面說說理解
---1####
,首先定義幾個(gè)變量,utils, nextGuid, Channel, channel , utils就是前面兩篇文章分析的工具模塊;nextGuid 就是一個(gè)Guid; Channel是一個(gè)普通的構(gòu)造器方法;channel則是最終返回的結(jié)果。
注意到下面這一段代碼(將所有具體邏輯省略) , 將Channel的定義及其原型的修改放在一起,我們可以看到一個(gè)典型的創(chuàng)建對(duì)象的方法:通過構(gòu)造器初始化內(nèi)部變量,從而讓各個(gè)實(shí)例互相獨(dú)立,通過修改函數(shù)原型共享實(shí)例方法。
var Channel = function(type, sticky) {
// 通過構(gòu)造器初始化內(nèi)部變量
},
channel = {
// 創(chuàng)建對(duì)象函數(shù)
Channel.prototype.subscribe = function(f, c) {
// subscribe(向事件通道注入事件處理函數(shù))
};
Channel.prototype.unsubscribe = function(f) {
// 解除事件處理函數(shù),反注入
};
Channel.prototype.fire = function(e) {
// fire(觸發(fā)所有注入的函數(shù))
};
---2####
subscribe是向通道注入事件處理函數(shù)的函數(shù),這個(gè)函數(shù),是負(fù)責(zé)將函數(shù)注入事件通道的、通過一個(gè)自增長GUID為注入函數(shù)的索引。
unsubscribe 是反注入,就是將通過subscribe注入到通道的函數(shù)刪除,通過guid,找到this. handler中保存的響應(yīng)函數(shù),并且刪除。
fire 調(diào)用、觸發(fā)注入函數(shù), 主要是修改 sticky事件狀態(tài),構(gòu)造調(diào)用函數(shù)的數(shù)組, 并且一次調(diào)用所有事件處理函數(shù)(利用了apply()函數(shù)實(shí)例方法調(diào)用 )
4. cordova 工具類 (utils)
// file: src/common/utils.js
define("cordova/utils", function(require, exports, module) {
//exports是一個(gè){ }空對(duì)象, 賦值給utils
var utils = exports;
/**
* Defines a property getter / setter for obj[key].
*/
utils.defineGetterSetter = function(obj, key, getFunc, opt_setFunc) {
if (Object.defineProperty) {
var desc = {
get: getFunc,
configurable: true
};
if (opt_setFunc) {
desc.set = opt_setFunc;
}
Object.defineProperty(obj, key, desc);
} else {
obj.__defineGetter__(key, getFunc);
if (opt_setFunc) {
obj.__defineSetter__(key, opt_setFunc);
}
}
};
/**
* 通過 obj[key]. 定義一個(gè)Getter方法(內(nèi)部調(diào)用utils.defineGetterSetter)
*/
utils.defineGetter = utils.defineGetterSetter;
utils.arrayIndexOf = function(a, item) {
if (a.indexOf) {
return a.indexOf(item);
}
var len = a.length;
for (var i = 0; i < len; ++i) {
if (a[i] == item) {
return i;
}
}
return -1;
};
/**
* 返回該項(xiàng)目是否在數(shù)組中。
*/
utils.arrayRemove = function(a, item) {
var index = utils.arrayIndexOf(a, item);
if (index != -1) {
a.splice(index, 1);
}
return index != -1;
};
utils.typeName = function(val) {
return Object.prototype.toString.call(val).slice(8, -1);
};
/**
* 判斷一個(gè)對(duì)象是否為Array類型
*/
utils.isArray = Array.isArray ||
function(a) {return utils.typeName(a) == 'Array';};
/**
* 判斷一個(gè)對(duì)象是否為Date類型
*/
utils.isDate = function(d) {
return (d instanceof Date);
};
/**
* 深度copy一個(gè)對(duì)象,包括這個(gè)對(duì)象的事件等
*/
utils.clone = function(obj) {
if(!obj || typeof obj == 'function' || utils.isDate(obj) || typeof obj != 'object') {
return obj;
}
var retVal, i;
if(utils.isArray(obj)){
retVal = [];
for(i = 0; i < obj.length; ++i){
retVal.push(utils.clone(obj[i]));
}
return retVal;
}
retVal = {};
for(i in obj){
if(!(i in retVal) || retVal[i] != obj[i]) {
retVal[i] = utils.clone(obj[i]);
}
}
return retVal;
};
/**
* 對(duì)一個(gè)函數(shù)的包裝調(diào)用
*/
utils.close = function(context, func, params) {
return function() {
var args = params || arguments;
return func.apply(context, args);
};
};
//------------------------------------------------------------------------------
// 內(nèi)部私有函數(shù) , UUIDcreatePart函數(shù)用來隨機(jī)產(chǎn)生一個(gè)16進(jìn)制的號(hào)碼,接受一個(gè)表示號(hào)碼長度的參數(shù)(實(shí)際上是最終號(hào)碼長度的一半),一般用途是做為元素的唯一ID;
function UUIDcreatePart(length) {
var uuidpart = "";
for (var i=0; i<length; i++) {
var uuidchar = parseInt((Math.random() * 256), 10).toString(16);
if (uuidchar.length == 1) {
uuidchar = "0" + uuidchar;
}
uuidpart += uuidchar;
}
return uuidpart;
}
/**
* 產(chǎn)生隨機(jī)字符串 (UUID)
*/
utils.createUUID = function() {
return UUIDcreatePart(4) + '-' +
UUIDcreatePart(2) + '-' +
UUIDcreatePart(2) + '-' +
UUIDcreatePart(2) + '-' +
UUIDcreatePart(6);
};
/**
* 繼承子對(duì)象
*/
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;
};
}());
/**
* 彈出消息,不支持彈出消息時(shí),寫日志到控制臺(tái)
*/
utils.alert = function(msg) {
if (window.alert) {
window.alert(msg);
} else if (console && console.log) {
console.log(msg);
}
};
});
附上JS一些函數(shù)的解析
(1)push():接受任意數(shù)量的參數(shù),逐個(gè)添加到數(shù)組末尾,并返回修改后數(shù)組長度
(2)pop():移除最后一項(xiàng),修改數(shù)組長度,返回被移除的項(xiàng)
(3)shift():移除數(shù)組第一項(xiàng),修改數(shù)組長度,返回被移除的項(xiàng)
(4)unshift():在數(shù)組前端添加任意個(gè)項(xiàng),并返回?cái)?shù)組長度
(5)slice():基于當(dāng)前數(shù)組的一個(gè)或多個(gè)項(xiàng)創(chuàng)建一個(gè)新數(shù)組,可以接受1或2個(gè)參數(shù),即要返回項(xiàng)的起始和結(jié)束位置,只有一個(gè)參數(shù)時(shí),返回從該參數(shù)指定位置開始到末尾,有兩個(gè)參數(shù),返回這兩個(gè)參數(shù)之間項(xiàng)(前閉后開區(qū)間),slice不會(huì)影響原數(shù)組。若參數(shù)為負(fù)數(shù),則用數(shù)組長度加上參數(shù)直至為正數(shù),若結(jié)束位置小于起始位置,返回空數(shù)組。
(6)splice():返回從原數(shù)組中刪除的項(xiàng)構(gòu)成的數(shù)組,若沒有刪除,返回空數(shù)組
刪除:可以刪除任意數(shù)量的項(xiàng),只需指定2個(gè)參數(shù),要?jiǎng)h除的第一項(xiàng)的位置和要?jiǎng)h除的項(xiàng)數(shù),如splice(0,2)會(huì)刪除前面兩項(xiàng)
插入:可以向指定位置插入任意數(shù)量的項(xiàng),只需提供3個(gè)參數(shù),起始位置,0(要?jiǎng)h除的數(shù)),要插入的項(xiàng),如果要插入多個(gè)項(xiàng),可以傳入第四、第五以至任意多個(gè)項(xiàng)
替換:可以向指定位置插入任意數(shù)量的項(xiàng),且同時(shí)刪除任意數(shù)量的項(xiàng)