你的位置:首页 > Java教程

[Java教程]理解《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里面来,从而能使用args106 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.trigger139         }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将其绑定到第一个参数上面呢?希望看到的大大能帮我解释一下,谢谢!