标签:
最近拜读了曾探所著的《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将其绑定到第一个参数上面呢?希望看到的大大能帮我解释一下,谢谢!
理解《JavaScript设计模式与开发应用》发布-订阅模式的最终版代码
标签:
原文地址:http://www.cnblogs.com/Jewl/p/5443154.html