分析完了cordova/utils之后,回到cordova/channel這個模塊來,這個模塊是實現事件監聽的基礎,當然,我們的焦點是它的構造函數,源碼中是匿名的,這里為了行文方便,姑且稱之為factory。
要分析一個函數,從外部來說,知道怎么調用它就行了,這也就是通常所說的暴露在外的API,我們知道,factory是作為一個參數來傳遞給define函數的,並在第一次require中實際調用的,之后就清除了這個構造函數,回過頭來看看這個調用的代碼:
1 function build(module) { 2 var factory = module.factory; 3 module.exports = {}; 4 delete module.factory; 5 factory(require, module.exports, module); 6 return module.exports; 7 }
實際調用在第5行,傳入三個參數,沒有使用返回值,調用之后返回的是module.exports,而這正是其中一個傳入參數,我們可以由此判斷,在factory這個函數內部,module.exports被修飾,因此,下面在分析factory時,需要重點關注module.exports這個參數(channel)的變更。
從外部調用角度來分析函數,雖然也是一個不錯的方法,尤其是對不關注具體實現的時候,但是,也可能會有以偏概全的不利影響,畢竟,很多時候外部調用很難窮盡所有用法。現在,我們再從內部代碼來分析一下factory。
1 function(require, exports, module) { 2 var utils = require('cordova/utils'); 3 4 var Channel = function(type, opts) { 5 }, 6 channel = { 7 }; 8 9 function forceFunction(f) { 10 } 11 12 Channel.prototype.subscribe = function(f, c, g) { 13 }; 14 15 Channel.prototype.subscribeOnce = function(f, c) { 16 }; 17 18 Channel.prototype.unsubscribe = function(g) { 19 }; 20 21 Channel.prototype.fire = function(e) { 22 }; 23 24 channel.create('onDOMContentLoaded'); 25 26 channel.create('onNativeReady'); 27 28 channel.create('onCordovaReady'); 29 30 channel.create('onCordovaInfoReady'); 31 32 channel.create('onCordovaConnectionReady'); 33 34 channel.create('onDeviceReady'); 35 36 channel.create('onResume'); 37 38 channel.create('onPause'); 39 40 channel.create('onDestroy'); 41 42 channel.waitForInitialization('onCordovaReady'); 43 channel.waitForInitialization('onCordovaConnectionReady'); 44 45 module.exports = channel; 46 47 }
1、從返回值上看,這里沒有明確的返回,但是根據前面的分析,我們知道,第45行的module.exports = channel;就相當於是將channel這個變量作為返回值返回了。
2、從結構上看,這個構造函數,先是定義了三個內部變量utils、Channel、channel,然后是修改Channel的原型,接着使用channel創建一系列事件並最終返回。
3、再從動態執行的角度來看一下:
(1)當然,由於函數聲明提升,首先是定義第9行開始的函數forceFunction:
function forceFunction(f) { if (f === null || f === undefined || typeof f != 'function') throw "Function required as first argument!"; }
這個函數很簡單,就是判斷傳入的參數f是不是一個函數,如果不是就拋出異常。這里稍微提一下等號“==”和全等號“===”的區別,等號在比較之前,如果需要,會自動轉換數據類型,而全等號不會自動轉換類型,比如'2'==2而'2' !==2。
(2)第2-7行是定義三個內部變量並初始化,utils就是前面兩篇文章分析的工具模塊;Channel是一個普通的構造器方法;channel則是最終返回的結果,是通過對象字面量定義的。
4、將Channel的定義及其原型的修改放在一起,我們可以看到一個典型的創建對象的方法:通過構造器初始化內部變量,從而讓各個實例互相獨立,通過修改函數原型共享實例方法。原型中定義了subscribe、unsubscribe、subscribeOnce、fire四個方法:
(1)、subscribe(向事件通道注入事件處理函數)
1 Channel.prototype.subscribe = function(f, c, g) { 2 // need a function to call 3 forceFunction(f); 4 5 var func = f; 6 if (typeof c == "object") { func = utils.close(c, f); } 7 8 g = g || func.observer_guid || f.observer_guid; 9 if (!g) { 10 // first time we've seen this subscriber 11 g = this.guid++; 12 } 13 else { 14 // subscriber already handled; dont set it twice 15 return g; 16 } 17 func.observer_guid = g; 18 f.observer_guid = g; 19 this.handlers[g] = func; 20 this.numHandlers++; 21 if (this.events.onSubscribe) this.events.onSubscribe.call(this); 22 if (this.fired) func.call(this); 23 return g; 24 };
subscribe是向通道注入事件處理函數並返回一個自增長的ID(可以用來反注入或內部查找),多次注入會直接返回。在第8行有一個用法:g = g || func.observer_guid || f.observer_guid,也就是取第一個為true的值(null、undefined、false、0都視為false),第一次會將函數注入通道,並且觸發注入事件(如果有),然后如果需要立即觸發,則再觸發注入的函數。
(2)unsubscribe(解除事件處理函數,反注入)
Channel.prototype.unsubscribe = function(g) { // need a function to unsubscribe if (g === null || g === undefined) { throw "You must pass _something_ into Channel.unsubscribe"; } if (typeof g == 'function') { g = g.observer_guid; } var handler = this.handlers[g]; if (handler) { if (handler.observer_guid) handler.observer_guid=null; this.handlers[g] = null; delete this.handlers[g]; this.numHandlers--; if (this.events.onUnsubscribe) this.events.onUnsubscribe.call(this); } };
反注入比較簡單,就是將前面注入的處理函數給刪除,並且觸發反注入事件(如果有),可以傳入注入的函數,或者傳入注入函數時返回的ID。
(3)subscribeOnce(注入事件處理,但是只調用一次,然后就自動解除和之間的關聯關系)
Channel.prototype.subscribeOnce = function(f, c) { // need a function to call forceFunction(f); var g = null; var _this = this; var m = function() { f.apply(c || null, arguments); _this.unsubscribe(g); }; if (this.fired) {//立即觸發,就直接調用 if (typeof c == "object") { f = utils.close(c, f); } f.apply(this, this.fireArgs); } else {//注入“調用並反注入”的函數 g = this.subscribe(m); } return g; };
注入只調用一次的函數,如果需要立即觸發,實際上只需要觸發而不需要注入,不需要立即觸發,將原函數調用和反注入原函數作為一個新的事件處理函數注入。
(4)fire(觸發所有注入的函數)
Channel.prototype.fire = function(e) { if (this.enabled) { var fail = false; this.fired = true; for (var item in this.handlers) { var handler = this.handlers[item]; if (typeof handler == 'function') { var rv = (handler.apply(this, arguments)===false); fail = fail || rv; } } this.fireArgs = arguments; return !fail; } return true; };
就是在當前狀態可用的情況下調用所有注入的事件處理函數。
在這里復習一下函數調用的兩種方式:
A、使用括號調用:functionName(args);
B、使用函數實例方法apply或call方法調用:如functionName.apply(scope,args)或functionName.call(scope, arg1, arg2,...,argn)。這里關鍵的是scope可用改變functionName這個函數的作用域。而apply和call的區別就是后面的參數,前者傳入數組或類數組(如arguments),后者需要將參數一一列舉。
5、返回值channel
先是通過對象字面量定義,然后創建一系列事件,注意的是,Channel的subscribe方法是同一個事件的多次處理方法,而這里創建的則是初始啟動的一系列事件。這些事件分別是:onDOMContentLoaded DOM文檔已經加載並解析
onNativeReady Cordova本地已經就緒
onCordovaReady Cordova中所有Javascript對象已經創建完畢,開始運行插件
onCordovaInfoReady 設備屬性可以訪問
onCordovaConnectionReady Cordova連接已經設置好
onDeviceReady Cordova已經加載完成
onResume 啟動/恢復
onPause 暫停
onDestroy 應用銷毀(通常會使用window.onload)