最近拜讀了曾探所著的《JavaScript設計模式與開發應用》一書,在讀到發佈-訂閱模式一章時,作者不僅給出了基本模式的通用版本的發佈-訂閱模式的代碼,最後還做出了擴展,給該模式增加了離線空間功能和命名空間功能,以達到先發佈再訂閱的功能和防止名稱衝突的效果。但是令人感到遺憾的是最終代碼並沒有給出足夠 ...
最近拜讀了曾探所著的《JavaScript設計模式與開發應用》一書,在讀到發佈-訂閱模式一章時,作者不僅給出了基本模式的通用版本的發佈-訂閱模式的代碼,最後還做出了擴展,給該模式增加了離線空間功能和命名空間功能,以達到先發佈再訂閱的功能和防止名稱衝突的效果。但是令人感到遺憾的是最終代碼並沒有給出足夠的註釋。這讓像我一樣的小白就感到非常的困惑,於是我將這份最終代碼仔細研究了一下,並給出了自己的一些理解,鑒於能力有限,文中觀點可能並不完全正確,望看到的大大們不吝賜教,謝謝!
下麵是添加了個人註釋的最終版代碼:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title></title> 5 <meta charset = "utf-8" /> 6 </head> 7 <body> 8 <script type="text/javascript"> 9 var Event = (function(){ //定義立即調用的對象 10 var global = this, 11 Event, 12 _default = 'default'; 13 Event = function(){ 14 var _listen,//私有變數 15 _trigger, 16 _remove, 17 _slice = Array.prototype.slice, 18 _shift = Array.prototype.shift, 19 _unshift = Array.prototype.unshift, 20 namespaceCache = {}, 21 _create, 22 find, 23 each = function( ary, fn ){ 24 var ret; 25 for ( var i = 0, l = ary.length; i < l; i++ ){ 26 var n = ary[i]; 27 ret = fn.call( n, i, n); 28 //n(args) 29 } 30 return ret; 31 }; 32 _listen = function( key, fn, cache ){ 33 if ( !cache[ key ] ){ 34 cache[ key ] = []; 35 } 36 cache[key].push( fn ); 37 }; 38 _remove = function( key, cache ,fn){ 39 40 if ( cache[ key ] ){ 41 var fns = cache[key]; 42 if( fn ){ 43 for( var i = fns.length - 1; i >= 0; i-- ){ 44 //原文for( var i = cache[ key ].length; i >= 0; i-- ){ 45 //if( cache[ key ] === fn )我認為不妥。 46 if( fns[i] === fn ){ 47 fns.splice( i, 1 ); 48 } 49 } 50 }else{ 51 cache[ key ] = []; 52 } 53 } 54 }; 55 _trigger = function(){ 56 var cache = _shift.call(arguments), 57 key = _shift.call(arguments), 58 args = arguments, 59 _self = this, 60 ret, 61 stack = cache[ key ]; 62 if ( !stack || !stack.length ){ 63 return; 64 } 65 return each( stack, function(){ 66 return this.apply( _self, args );//_self = object{} //n(args) 67 }); 68 }; 69 _create = function( namespace ){ 70 var namespace = namespace || _default; 71 var cache = {}, 72 offlineStack = [], 73 // 離線事件 74 ret = { 75 listen: function( key, fn, last ){ 76 _listen( key, fn, cache ); 77 if ( offlineStack === null ){ 78 return; 79 } 80 if ( last === 'last' ){ 81 offlineStack.length && offlineStack.pop()(); 82 }else{ 83 each( offlineStack, function(){ 84 this(); 85 }); 86 } 87 offlineStack = null; 88 }, 89 one: function( key, fn, last ){ 90 _remove( key, cache ); 91 //移除已存在的listen事件 92 this.listen( key, fn ,last ); 93 }, 94 remove: function( key, fn ){ 95 _remove( key, cache ,fn); 96 }, 97 trigger: function(){ 98 var fn, 99 args, 100 _self = this; 101 _unshift.call( arguments, cache ); 102 args = arguments; 103 fn = function(){ 104 return _trigger.apply( _self, args ); 105 //_self的作用是將—trigger方法綁定到ret裡面來,從而能使用args 106 107 }; 108 if ( offlineStack ){ 109 return offlineStack.push( fn ); 110 } 111 return fn(); 112 } 113 }; 114 return namespace ? 115 ( namespaceCache[ namespace ] ? namespaceCache[ namespace ] : 116 namespaceCache[ namespace ] = ret ) 117 : ret; 118 }; 119 return { 120 //所有方法均先創建一個離線空間 調用create方法,並傳遞空參數, 返回ret = object{}; 121 create: _create, 122 one: function( key,fn, last ){ 123 var event = this.create( ); 124 event.one( key,fn,last ); 125 }, 126 remove: function( key,fn ){ 127 var event = this.create( ); 128 event.remove( key,fn ); 129 }, 130 listen: function( key, fn, last ){ 131 var event = this.create( ); 132 event.listen( key, fn, last ); 133 }, 134 trigger: function(){ 135 var event = this.create( ); 136 //event = ret ; 137 event.trigger.apply( this, arguments ); 138 //將arguments傳遞給ret.trigger 139 } 140 }; 141 }(); 142 return Event; 143 })(); 144 Event.trigger( 'click', 5 ); 145 // 將其存入offlineStack等待調用 146 Event.listen( 'click', function( a ){ 147 console.log( a ); 148 }); 149 Event.create( 'namespace1' ).listen( 'click', function( a ){ 150 console.log( a ); 151 }); 152 // namespace的作用是,沒有時,我們返回簡單的ret對象。有時,我們返回namespase下的一個鍵值為namespase1的對象 153 154 Event.create( 'namespace1' ).trigger( 'click', 1 ); 155 // 將調用namespase1的trigger方法 156 Event.one('click',function(a){ 157 console.log("this is the one's "+a); 158 } ,"last");
159 Event.trigger('click',666); 160 Event.listen( 'click', function( a ){ 161 console.log( "this is a simple" +a ); 162 }); 163 Event.listen( 'click', function( a ){ 164 console.log( "this is also a simple " +a ); 165 });
166 Event.trigger('click',"hahaha"); 167 Event.one('click',function(a){ 168 console.log("this is the one's "+a + " and it's the only " + a); 169 } ,"last"); 170 Event.trigger('click',"hahahahahahahaha"); 171 </script> 172 </body> 173 </html>
我認為對於代碼的理解可以分為兩個階段,第一個階段:理解代碼的含義,明白代碼是怎麼運行的;第二個階段:深刻理解代碼本質,並能夠獨立寫出代碼。當然作為小白的我還沒有能力達到第二階段,也只能講講自己第一階段的理解了。
有同學曾和我討論過這段代碼裡面one()的作用,通過最後面添加的實例不難理解,它的作用是清除之前存在的(某個命名空間的)訂閱事件,再添加唯一的一個訂閱事件。然後對於一些細節的理解我通過註釋添加在了代碼中,如有感興趣的同學,歡迎前來和我討論,或者有覺得我的觀點有失偏頗的,希望能不吝賜教。
最後我認為拋開個模塊之間有點複雜的通信外,這段代碼最讓人難以理解的就是this的應用了,JavaScript裡面的this被認為是一個巨大的坑,但運用得當,必會事半功倍。這裡我先簡要談談對this的理解:
JavaScript裡面的this大致可以分為4種情況:第一種情況,方法調用模式:函數被保存為對象的一個方法,當這個方法被調用時,this指向該對象;第二種情況,函數調用模式:此模式下,this被綁定到全局對象,這被認為是一個設計錯誤;第三種情況,構造器調用模式:如果一個函數前面帶上new來調用,那麼將創建一個隱藏鏈接到該函數的prototype成員的新對象,同時this綁定到新對象上;第四種情況,call,apply調用模式:該模式類似於繼承,將執行call,apply操作的對象綁定到第一個參數上,同時將this綁定到第一個參數上,例如:
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script type="text/javascript"> var name = "I am window"; var obj = { name:"sharpxiajun", job:"Software", ftn01:function(obj){ obj.show(); }, ftn02:function(ftn){ ftn(); }, ftn03:function(ftn){ ftn.call(this); } }; function Person(name){ this.name = name; this.show = function(){ console.log("姓名:" + this.name); console.log(this); } } var p = new Person("Person"); obj.ftn01(p); obj.ftn02(function(){ console.log(this.name); console.log(this); }); obj.ftn03(function(){ console.log(this.name); console.log(this); }); </script> </body> </html>
實例來源: 夏天的森林 《JavaScript技術難點(三)之this、new、apply和call詳解》
輸出結果為:
但是發佈-訂閱模式的的this應用依然讓我感到費解:
這兩處this的使用不像平常見到的那種隱式調用或者用做參數,而是直接當做函數使用(表述不定對),這讓我有點難以理解,但是他們達到的效果就是類似的,起到的是傳遞參數的作用。那麼這裡的this我否可以理解為也是通過call,apply將其綁定到第一個參數上面呢?希望看到的大大能幫我解釋一下,謝謝!