4(phonegap源碼分析)通道模塊的事件訂閱機制(channel)

        channel模塊式所有模塊中比較核心的一個模塊,它定義一個通道,在這個通道上有一個事件,你可以訂閱這個事件,當這個事件被激發的時候,所有註冊在這個通道上的訂閱者都能收到。這裏的訂閱者其實就是一個函數,當事件激發,函數就會被調用。

        下面是channel的工廠函數的主體代碼:

  function(require, exports, module) {
  var utils = require('myphonegap/utils');
  
  var Channel = function(type, opts) {
  },
      channel = {
       };
  
  function forceFunction(f) {
  }
  
  Channel.prototype.subscribe = function(f, c, g) {
  };
  Channel.prototype.subscribeOnce = function(f, c) {
   };
  Channel.prototype.unsubscribe = function(g) {
   };
  Channel.prototype.fire = function(e) {
  };
  
  channel.create('onDOMContentLoaded');
  channel.create('onNativeReady');
  channel.create('onMyphonegapReady');
  channel.create('onMyphonegapInfoReady');
  channel.create('onMyphonegapConnectionReady');
  channel.create('onDeviceReady');
  channel.create('onResume');
  channel.create('onPause');
  channel.create('onDestroy');
  
  channel.waitForInitialization('onMyphonegapReady');
  channel.waitForInitialization('onMyphoneConnectionReady');
  module.exports = channel;
}

        上面的代碼是JS中比較經典的實現的面向對象的方法:將成員變量定義在構造函數中,將成員函數定義在構造函數的原型對象中。這樣通過new方法實例出來的對象,成員變量是相互獨立的,而成員函數通過原型對象共享。

        Channel這個函數對象可以看做是一個通道類的構造函數,最重要的兩個成員變量type存儲事件類型,handlers存儲事件訂閱者(函數)。

var Channel = function(type,sticky){
	this.type = type;
	//map of guid -> function
	this.handlers = {};
	// 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired.
	this.state = sticky?1:0;
	// Used in sticky mode to remember args passed to fire().
	this.fireArgs = null;
	// Used by onHasSubscribersChange to know if there are any listeners.
	this.numHandlers = 0;
	// Function that is called when the first listener is subscribed, or when
	// the last listener is unsubscribed.
	this.onHasSubscribersChange = null;
}

        this指針指向的是當前執行環境,所以通過new構造新的對象的時候,this指向的就是這個新生成的對象,而之後函數原型對象中的函數執行的時候,函數中的this指針執行的就是調用它們的對象。但是Channel當做一個函數執行時,this指針指向的就是全局執行環境,也就是window這個對象,調用Channel也不能返回一個對象。

        下面再分析下channel這個對象,注意這個channel是小寫字母開頭。先上代碼:

		 channel={
			create:function(type){
				channel[type] = new Channel(type,false);
			},
			createSticky:function(type){
				channel[type] = new Channel(type,true);
			},
			deviceChannelsArray:[],
			deviceChannelsMap:{},
			waitForInitialization:function(feature){
				if(feature){
					var c = channel[feature]||this.createSticky(feature);
					this.deviceChannelsArray.push(c);
					this.deviceChannelsMap[feature]=c;
				}
			},
			initializationComplete:function(feature){
				var c = this.deviceChannelsMap[feature];//
				if(c){
					c.fire();
				}
			},
			join: function (h, c) {//join也就是將一組Channel連接起來,並注入一個在所有Channel上只執行一次的公共處理函數
				var i = c.length;
				var len = i;
				var f = function() {
					if (!(--i)) h();
				};
				for (var j=0; j<len; j++) {
					!c[j].fired?c[j].subscribeOnce(f):i--;
				}
				if (!i) h();
			}
		};

        如果只看名字,一般人都以爲channelChannel的一個實例,我開始也是這樣認爲的;而事實上它根本不是Channel的實例,如果勉強一點可以把它看做Channel實例的集合,之所以說勉強,是因爲它還提供了一些函數。我想之所以它也取名叫做channel而不叫channelMapchannels的原因是它最終是作爲通道模塊的導出對象的。

        這個channel對象的功能:通過create方法構造各個通道實例,然後可以動過這個對象訪問到他已生成的對象實例。比如channel.create('onDeviceReady')創建通道實例之後,就可以通過channel.onDeviceReady來訪問這個Channel實例了,每個Channel提供了subscribeunsubscribesubscribeOncefire四個方法。

        如果採用其他語言實現channl這個對象功能,就會寫一個ChannelManager的單例。它提供一個create方法,通過不同的通道名生成Channel實例,然後將通道名與實例作爲一個鍵值對存儲在Map中,通過一個get函數來訪問對應的Channel實例。

ChannelManager.getInstance().create(“onDeviceReady”);
Channel  onDeviceReady = ChannelManager.getInstance().get(“onDeviceReady”);

        至於channel提供的waitForInitialization、和initializationComplete這兩個函數,一個是等待初始化,一個是初始化完成。等待初始化將根據名稱實例化通道加入到一個表中,調用初始化完成函數就激發這些通道事件。

        join函數很費解,暫不分析...

       下面進行測試,先請求該模塊實例,然後再該模塊實例的onNativeReady通道對象上註冊一個函數,函數內容就是打印一條信息。手動觸發這個通道事件,看註冊的函數是否執行。

//測試channel模塊
	var channel = require("myphonegap/channel");
		channel.onNativeReady.subscribe(function(){
			console.info("onNativeReady");
		}
	);
	console.info("before native ready");
	channel.onNativeReady.fire();
	console.info("after native ready");

 測試結果::


myphonegap.js 源碼:

;(function(){
  var  require,//myphonegap內部的工具函數,用來導入相關的模塊
        define;//在myphonegap註冊相關的模塊

  //通過一個立即調用的匿名函數,來給require和define賦上實際的函數
  (function(){
		var modules={};   // 模塊數組,添加模塊類似給這個對象添加了屬性,模塊名爲屬性名,模塊對象爲屬性值,或者說是鍵值對
				
		build = function(module){        //根據模塊對象構造模塊導出對象,模塊導出對象存儲在modules這個對象數組內
			var factory = module.factory;
			module.exports = {};           //給當前模塊加入了一個exports屬性
			delete module.factory;		   //刪除了module的屬性
			factory(require,module.exports,module);     //構建導出模塊,module.exports是傳出參數(實參,引用傳遞)
			return module.exports;
		}
		
		require = function(id){            //根據模塊名稱/id請求模塊對象,如果是第一次請求,就構建對象
			if(!modules[id]){
				throw "module " + id + " not found!";
			}
			return modules[id].factory?build(modules[id]):modules[id].exports;
		}
	
		define = function(id,factory){		 //定義模塊,模塊名稱、構建模塊對象的工廠方法。
			if(modules[id]){                  
				throw "module " + id + " is exist!";
			}
			modules[id] = {					//定義模塊對象,左邊的值爲屬性名,右邊的值爲傳入的參數 
				id:id,						
				factory:factory
			};
		}
  })();

  //註冊myphonegap模塊
  define("myphonegap", function(require, exports, module){
		console.info("create myphonegap module");
		var myphonegap = {
			Hello:function(name){
				console.info("hello, "+name +" !");
			}
		};
		
		module.exports = myphonegap;
  });

  //註冊myphonegap/builder模塊
	define("myphonegap/builder", function(require, exports, module) {

	});

   define("myphonegap/channel",function(require,exports,module){
		var utils = require("myphonegap/utils"),
			nextGuid = 1;
		//典型的創建對象方法:通過構造器初始化變量,從而讓各個實例相互獨立;之後通過修改函數原型共享實例方法。
		var Channel = function(type,sticky){
			this.type = type;
			//map of guid -> function
			this.handlers = {};
			// 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired.
			this.state = sticky?1:0;
			// Used in sticky mode to remember args passed to fire().
			this.fireArgs = null;
			// Used by onHasSubscribersChange to know if there are any listeners.
			this.numHandlers = 0;
			// Function that is called when the first listener is subscribed, or when
			// the last listener is unsubscribed.
			this.onHasSubscribersChange = null;
	 
		}, channel={
			create:function(type){
				channel[type] = new Channel(type,false);
			},
			createSticky:function(type){
				channel[type] = new Channel(type,true);
			},
			deviceReadyChannelsArray: [],
            deviceReadyChannelsMap: {},
			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();
				}
			},
			join: function (h, c) {//join也就是將一組Channel連接起來,並注入一個在所有Channel上只執行一次的公共處理函數
				var i = c.length;
				var len = i;
				var f = function() {
					if (!(--i)) h();
				};
				for (var j=0; j<len; j++) {
					!c[j].fired?c[j].subscribe(f):i--;
				}
				if (!i) h();
			}
		};
	
		function forceFunction(f){
			if (f === null || f === undefined || typeof f != 'function') throw "Function required as first argument!";
		}
		//給對象Channel的原型對象添加訂閱函數,參數:函數對象、上下文、全局唯一ID
		Channel.prototype.subscribe = function(f,c,g){
			forceFunction(f);//確保f爲函數
			
			if(this.state==2){	//apply方法能劫持另外一個對象的方法,繼承另外一個對象的屬性;f裏面的指針爲c(Channel的實例)
				f.apply(c||this,this.fireArgs);
			}
			
			var func = f,
				guid = f.observer_guid;
			if(typeof f == "object"){ func = utils.close(c,f);}
			
			if(!guid){
				guid = ''+ nextGuid++;
			}
			f.observer_guid = guid;
			func.observer_guid = guid;
			
			//防止重複添加
			if(!this.handlers[guid]){
				this.handlers[guid] = func;
				this.numHandlers++;
				if(this.numHandlers===1){
					this.onHasSubscribersChange&&this.onHasSubscribersChange();
				}
			}
			
		};
		Channel.prototype.unsubscribe = function(f){
			forceFunction(f);
			var guid = f.observer_guid;
			if(this.handlers[guid]){
				delete this.handlers[guid];
				this.numHandlers--;
				if(numHandlers===0){
					this.onHasSubscribersChange&&this.onHasSubscribersChange();
				}
			}
		};
		//訂閱一次
		Channel.prototype.subscribeOnce = function(f,c,g){
		
		};
		//調用了在通道訂閱的所有函數
		Channel.prototype.fire = function(e){
			//console.info('fire start:type/'+this.type + ' numHandlers/' +this.numHandlers); 
			var fail = false,
			fireArgs = Array.prototype.slice.call(arguments);
			// Apply stickiness.
			if (this.state == 1) {
				this.state = 2;
				this.fireArgs = fireArgs;
			}
			if (this.numHandlers) {
				// Copy the values first so that it is safe to modify it from within
				// callbacks.
				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);
					
				//	console.info(this.type+' enter func fire ');
				}
				if (this.state == 2 && this.numHandlers) {
					this.numHandlers = 0;
					this.handlers = {};
					this.onHasSubscribersChange && this.onHasSubscribersChange();
				}
			}
		};
		
		channel.create('onDOMContentLoaded');
		channel.create('onNativeReady');
		channel.create('onDeviceReady');
		
		channel.waitForInitialization('onMyphonegapReady');
		channel.waitForInitialization('onMyphoneConnectionReady');

		module.exports = channel;
		
		//console.info('define myphonegap/channel completed');
	});

  //註冊myphonegap/common模塊
 	//配置對象,將公共模塊組織起來
	define("myphonegap/common",function(require,exports,module){
	});

 
	define("myphonegap/exec", function(require, exports, module) {
	});

		

  //註冊myphonegap/platform模塊
  define("myphonegap/platform", function(require, exports, module){
  });

  // 這裏省略了其它插件的註冊

  //註冊myphonegap/utils模塊
  define("myphonegap/utils", function(require, exports, module){
	});

	(function (context) {
	}(window));
  //所有模塊註冊完之後,再導入myphonegap至全局環境中
  //window.myphonegap = require('myphonegap');
	//window.myphonegap.Hello("wen");
	
	//測試channel模塊
	var channel = require("myphonegap/channel");
		channel.onNativeReady.subscribe(function(){
			console.info("onNativeReady");
		}
	);
	console.info("before native ready");
	channel.onNativeReady.fire();
	console.info("after native ready");
})();

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章