PhoneGap源碼分析7——cordova/channel


  分析完了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)

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM