1. 程式人生 > >那些年 你需要解決的觀察者模式/釋出-訂閱模式(ES6語法)

那些年 你需要解決的觀察者模式/釋出-訂閱模式(ES6語法)

在筆試和一些面試經驗交流的帖子中程序出現訂閱者模式,想著自己還不是很清楚,所以得解決,但是我們的目的應該不僅僅是對付筆試和麵試,應該瞭解這個模式的思想:鬆散耦合程式碼形式!更多的大家看別的描述吧,會比我更清楚,這裡也不累述,只是加入自己更加通俗的理解:我們的_events是一個可以看成是一個庫裡面包括了所有的訂閱者的資訊,資訊就是訂閱者的type,我把type當做是訂閱者的姓名,然後物件的type(訂閱者)有不同的fn(訂閱的內容),這樣也許會好理解一些

我在原作者上進行了語法的簡單精簡,剔除了執行作用域的部分(context),並且在作者可能沒有提到的點做了相對而言比較詳細的註釋,另外本文需要有一定的ES6語法的底子,比如說:箭頭函式,展開符(...),ES6中class類,Object.create(),findIndex(),引數傳遞預設值等等; 另外"_"的符號是約定俗成的表示,代表該變數是給內部使用的,但是更多時候見仁見智吧...

//自定義便捷的資料型別判斷函式
const isType=obj=>Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
const isArray=obj=>Array.isArray(obj)||isType(obj)==='array';
const isNullOrUndefined=obj=>obj===null||obj===undefined;

//內部函式鉤子,不對外開放;
//主要用於處理初始值,初始化物件EventEmitter的_events內容;
function _addListener(type,fn,once){
	if(typeof fn!=='function'){
		throw new Error("fn must to be a function");
	}
	fn.once=!!once;//轉譯once不傳值則預設為false;
	//以下做了三個判斷:
	//1是處理_events對應的type為空時直接賦值;
	//2是_events的對應的type已經有值且是函式,則加新加入的fn和之前的fn組成陣列
	//3是在已經是陣列(經過1和2的條件之後也就是說到這個判斷將會加入第三個fn)直接push則可
	//最後返回處理完成的物件 就是this
	const events=this._events[type];
	if(isNullOrUndefined(events)) this._events[type]=fn;
	else if(typeof events==='function') this._events[type]=[events,fn];
	else if(isArray(events)) this._events.push(fn);
	return this;
}
//構建EventEmitter類 ES6語法
//Object.create(null)
class EventEmitter{
	constructor() {
	    if(this._events===undefined){
	    	//目的是構建一個沒有原型鏈的空物件和直接this._events={}有很大的不同
	    	//賦值{}將會繼承js物件中很多的物件的方法,但我們不需要
	    	this._events=Object.create(null);
	    }
	}	
	addListener(type,fn,once=false){//大方向上(包含小方向on和once)增加一個訂閱者.預設once為false;
		return _addListener.call(this,type,fn,once);
	}
	on(type,fn){
		return this.addListener(type,fn);
	}
	once(type,fn){//和on的唯一不同時傳值多了個true
		return this.addListener(type,fn,true);
	}
	//核心函式之一:處理即釋出訊息(根據傳入的不同的type以及其中不同的fn進行釋出)
	emit(type,...fn){
		//處理type不存在或者type中沒有fn的情況
		const events=this._events[type];
		if(isNullOrUndefined(type)) throw new Error('This type has no function,please add a function for the type first');
		if(isNullOrUndefined(events)) throw new Error("Can't find any event");
		//由於我們最開始的時候_events中是一個fn則表現為函式;而多個則表現為函式陣列,所以分情況解決(在其中判斷fn是否為once,是則釋出一次就刪除)
		if(typeof events==='function'){
			events(fn);
			if(events.once) this.removeListener(type,events);
		}
		else if(isArray(events)){
			events.map(e=>{
				e(fn);
				if(e.once) this.removeListener(type,e);
			});
		}
		//這個地方我和原作者不同的是:我返回的還是該物件這樣可以鏈式呼叫;
		return this;
	}
	//核心函式之二:刪除某個訂閱者或者其要求的內容
	removeListener(type,fn){
		//處理不存在的情況
		if (isNullOrUndefined(this._events)||isNullOrUndefined(type)) return this;
		if(typeof fn!=='function') throw new Error ('fn must be a function');//可以考慮去掉,因為我們加入的時候已經要求fn必須是函式;
		const events=this._events[type];
		//這裡原作者特意強調了當該_events中某個type只有一個fn時,刪除它順帶就刪除了整個type,這樣可以防止記憶體洩漏,避免一堆沒用fn的type的存在
		if(typeof events==='function') events===fn && delete this._events[type];
		//找對應的type中的fn刪除即可
		else{
			const findIndex=events.findIndex(e=>e===fn);
			if(findIndex===-1) return this;
			if(findIndex===0) events.shift();
			else events.splice(findIndex,1);
			//進行降操作,當fn陣列只有一個fn時去掉陣列;和最開始的_addListener中的處理相互呼應
			//這麼做是因為能夠更快的執行js,提高使用者體驗吧,雖然我覺得對計算機來說這點優化微乎其微,也許是沒達到這種極致的境界吧,思路還是值得學習的
			if(events.length===1) this._events[type]=events[0];
		}
		return this;		
	}
	//後面的沒什麼可以說的了,大家看看程式碼就行了
	removeAllListener(type){
		if (isNullOrUndefined(this._events)) return this;
		if (isNullOrUndefined(type)) this._events = Object.create(null);
		const events=this._events[type];
 		if(!isNullOrUndefined(events)){
 			Object.keys(this._events).length === 1?this._events=Object.create(null):delete this._events[type];			
 		}
 		return this;
	}
	listeners(type) {
	  if (isNullOrUndefined(this._events)) return [];
	  const events = this._events[type];
	  // use `map` because we need to return a new array
	  return isNullOrUndefined(events) ? [] : (typeof events === 'function' ? [events] : events.map(o => o));
	}	 
	listenerCount(type) {
	  if (isNullOrUndefined(this._events)) return 0;	 
	  const events = this._events[type];	 
	  return isNullOrUndefined(events) ? 0 : (typeof events === 'function' ? 1 : events.length);
	}	 
	 eventNames() {
	  if (isNullOrUndefined(this._events)) return [];	 
	  return Object.keys(this._events);
	}
}