那些年 你需要解決的觀察者模式/釋出-訂閱模式(ES6語法)
阿新 • • 發佈:2018-12-11
在筆試和一些面試經驗交流的帖子中程序出現訂閱者模式,想著自己還不是很清楚,所以得解決,但是我們的目的應該不僅僅是對付筆試和麵試,應該瞭解這個模式的思想:鬆散耦合程式碼形式!更多的大家看別的描述吧,會比我更清楚,這裡也不累述,只是加入自己更加通俗的理解:我們的_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); } }