观察者模式:设计该模式背后的主要动力是促进形成松散耦合。在这种模式中,并不是一个对象调用另一个对象的方法,而是一个对象订阅另一个对象的特定活动并在状态改变后获得通知。订阅者也称为观察者,而补观察的对象称为发布者或主题。当发生了一个重要的事件时,发布者将会通知(调用)所有订阅者并且可能经常以事件对象的形式传递消息。
思路:发布者对象需要一个数组类型的属性,以存储所有的订阅者。订阅(即注册)行为就是将新的订阅者加入到这个数组中去,则注销即是从这个数组中删除某个订阅者。此外,发布消息,就是循环遍历订阅者列表并通知他们。
这里我的大体思路是对的,但是在发布者之外定义了一个新的类即订阅者。在订阅者中定义了一个方法getNews以便在发布者发布消息时调用该方法。然后面试官说这样太麻烦了,万一订阅者没有这个方法呢?然后我不是很懂……于是在发布消息时直接传递了参数:obj.news = msg; 然后面试官说这样不是更麻烦了吗?这样的话如果订阅者没有news这个属性怎么办?还得判断订阅者是否有news这个属性,没有的话就会出现undifined的报错。然后我就不知道该怎么做了……然后面试官为人特别nice,告诉我说可以用继承,或者是在注册时候就传入一个function。
下来后上网查相关,注意点如下:
1. 发送消息即通知,意味着调用订阅者对象的某个方法。故当用户订阅信息时,该订阅者需要向paper的subscribe()提供它的其中一个方法。--------这应该就是面试官所说的注册时候就传入一个方法。
2. 发布对象paper需要具有以下成员:
a、 subscribers:一个数组,存储订阅者
b、 subscribe():注册/订阅,将订阅者添加到subscribers数组中
c、 unsubscribe(): 取消订阅。从subscribers数组中删除订阅者
d、 publish() 循环遍历subscribers数组中的每一个元素,并且调用他们注册时所提供的方法
所有这三种方法都需要一个type参数,因为发布者可能触发多个事件(比如同时发布一本杂志和一份报纸)而用户可能仅选择订阅其中一种,而不是另外一种。
3、
参考《JavaScript模式》一书,使用字面量。实现代码如下:
1 //由于这些成员对于任何发布者对象都是通用的,故将它们作为独立对象的一个部分来实现是很有意义的。那样我们可将其复制到任何对象中,并将任意给定对象变成一个发布者。 2 3 //如下实现一个通用发布者 4 5 //定义发布者对象...{}是定义一个对象 6 var publisher = { 7 subscribers: { 8 any: [] //event type: subscribers 9 }, 10 subscribe: function(fn,type){ 11 type = type || 'any'; 12 if(typeof this.subscribers[type] === "undefined"){ 13 this.subscribers[type] = []; 14 } 15 this.subscribers[type].push(fn); 16 }, 17 unsubscribe: function(fn,type){ 18 this.visitSubscribers('unsubscribe', fn, type); 19 }, 20 publish: function(publication, type){ 21 this.visitSubscribers('publish',publication,type); 22 }, 23 visitSubscribers:function(action,arg,type){ 24 var pubtype = type ||'any', 25 subscribers = this.subscribers[pubtype], 26 i, 27 max = subscribers.length; 28 for(i=0;i<max;i++){ 29 if(action == "publish"){ 30 subscribers[i](arg); 31 } else { 32 if(subscribers[i] === arg){ 33 subscribers.splice(i,1); 34 } 35 } 36 } 37 } 38 }; 39 //定义一个函数makePublisher(),它接受一个对象作为对象,通过把上述通用发布者的方法复制到该对象中,从而将其转换为一个发布者 40 function makePublisher(o){ 41 var i; 42 for(i in publisher) { 43 if(publisher.hasOwnProperty(i) && typeof publisher[i] === "function"){ 44 o[i] = publisher[i]; 45 } 46 } 47 o.subscribers = {any: []}; 48 } 49 //实现paper对象 50 var paper = { 51 daily: function(){ 52 this.publish("big news today"); 53 }, 54 monthly: function(){ 55 this.publish("interesting analysis","monthly"); 56 } 57 }; 58 //将paper构造成一个发布者 59 makePublisher(paper); 60 //已经有了一个发布者。看看订阅对象joe,该对象有两个方法: 61 var joe = { 62 drinkCoffee: function(paper) { 63 console.log('Just read' + paper); 64 }, 65 sundayPreNap : function(monthly){ 66 console.log('About to fall asleep reading this' + monthly); 67 } 68 }; 69 //paper注册joe(即joe向paper订阅) 70 paper.subscribe(joe.drinkCoffee); 71 paper.subscribe(joe.sundayPreNap,'monthly'); 72 //即joe为默认“any”事件提供了一个可被调用的方法,而另一个可被调用的方法则用于当“monthly”类型的事件发生时的情况。现在让我们来触发一些事件: 73 paper.daily(); //Just readbig news today 74 paper.daily(); //Just readbig news today 75 paper.monthly(); //About to fall asleep reading thisinteresting analysis 76 paper.monthly(); //About to fall asleep reading thisinteresting analysis 77 paper.monthly();
自己又试着实现了一下
1 var publisher = { 2 subscribers: { 3 'any': [] 4 }, 5 subscribe: function(fn, type){ 6 var type = type || 'any'; 7 if(typeof this.subscribers[type] === 'undefined'){ 8 this.subscribers[type] = []; 9 } 10 this.subscribers[type].push(fn); 11 }, 12 unsubscribe: function(fn, type){ 13 this.visitSubscribers('unsubscribe', fn, type); 14 }, 15 publish: function(publication, type){ 16 this.visitSubscribers('publish', publication, type); 17 }, 18 visitSubscribers: function(action, arg, type){ 19 var type = type || 'any'; 20 if(typeof this.subscribers[type] === 'undefined'){ 21 this.subscribers[type] = []; 22 } 23 for(var i=0, len = this.subscribers[type].length; i<len; i++){ 24 if(action === 'unsubscribe'){ 25 if(this.subscribers[type][i] === arg){ 26 this.subscribers[type].splice(i,1); 27 } 28 } else { 29 this.subscribers[type][i](arg); 30 } 31 } 32 } 33 }; 34 35 function makePublisher(obj){ 36 var i; 37 for(i in publisher){ 38 if(publisher.hasOwnProperty(i) && typeof publisher[i] === 'function'){ 39 obj[i] = publisher[i]; 40 } 41 } 42 obj['subscribers'] = {'any':[]}; 43 } 44 45 var paper = { 46 daily: function(news){ 47 this.publish(news); 48 }, 49 monthly : function(news){ 50 this.publish(news, 'monthly'); 51 } 52 } 53 makePublisher(paper); 54 55 var Jack = { 56 drinkCoffee: function(publication){ 57 console.log('Jack read '+ publication +' while drinking coffe'); 58 }, 59 watchTV : function(publication){ 60 console.log('Jack read ' + publication + ' while watching TV'); 61 } 62 } 63 var Amy = { 64 AmyDrinkCoffee: function(publication){ 65 console.log('Amy read '+ publication +' while drinking coffe'); 66 }, 67 AmyWatchTV : function(publication){ 68 console.log('Amy read ' + publication + ' while watching TV'); 69 } 70 } 71 72 paper.daily(); 73 paper.monthly(); 74 paper.subscribe(Jack.drinkCoffee); 75 paper.subscribe(Jack.watchTV, 'monthly'); 76 paper.subscribe(Amy.AmyDrinkCoffee, 'monthly'); 77 paper.subscribe(Amy.AmyWatchTV); 78 79 console.log('paper发布daily'); 80 paper.daily(' today is Sunday '); //Jack read today is Sunday while drink coffe 81 //Amy read today is Sunday while watch TV 82 console.log('paper发布monthly'); 83 paper.monthly(' this month is Aug. '); //Jack read this month is Aug. while drink coffe 84 //Amy read this month is Aug. while watch TV 85 console.log('Amy取消了monthly的订阅'); 86 87 paper.unsubscribe(Amy.AmyWatchTV); 88 paper.daily(' today is Friday '); //Jack read today is Friday while drinking coffe 89 paper.monthly(' next weekend is Sept. '); //Jack read next weekend is Sept. while watching TV 90 //Amy read next weekend is Sept. while drinking coffe
试着用函数实现:
1 function Publisher(subscribers){ 2 this.subscribers = subscribers || {'any': []}; 3 Publisher.prototype.subscribe = function(fn, type){ 4 var type = type || 'any'; 5 if(typeof this.subscribers[type] === 'undefined'){ 6 this.subscribers[type] = []; 7 } 8 this.subscribers[type].push(fn); 9 }; 10 Publisher.prototype.unsubscribe = function(fn, type){ 11 var type = type || 'any'; 12 for(var i=0, len = this.subscribers[type].length; i<len; i++){ 13 if(this.subscribers[type][i] === fn){ 14 this.subscribers[type].splice(i,1); 15 } 16 } 17 }; 18 Publisher.prototype.publish = function(publication, type){ 19 var type = type || 'any'; 20 for(var i=0, len = this.subscribers[type].length; i<len; i++){ 21 this.subscribers[type][i](publication); 22 } 23 }; 24 } 25 26 27 var paper = new Publisher(); 28 paper.daily = function(){ 29 this.publish(' this is Olympic ! '); 30 }; 31 paper.monthly = function(){ 32 this.publish(' last month is the 28th Olympic! ', 'monthly'); 33 }; 34 35 var Jack = { 36 readInMorning: function(news){ 37 console.log('Jack reads ' + news + ' in the morning'); 38 }, 39 readInSunday: function(news){ 40 console.log('Jack reads ' + news + ' on Sunday'); 41 } 42 }; 43 44 var Amy = { 45 readInMorning: function(news){ 46 console.log('Amy reads ' + news + ' in the morning'); 47 }, 48 readInSunday: function(news){ 49 console.log('Amy reads ' + news + ' on Sunday'); 50 } 51 }; 52 53 paper.subscribe(Jack.readInMorning); 54 paper.subscribe(Jack.readInSunday, 'monthly'); 55 paper.subscribe(Amy.readInMorning); 56 paper.subscribe(Amy.readInSunday, 'monthly'); 57 58 paper.daily(); //Jack reads this is Olympic ! in the morning 59 //Amy reads this is Olympic ! in the morning 60 paper.monthly(); //Jack reads last month is the 28th Olympic! on Sunday 61 //Amy reads last month is the 28th Olympic! on Sunday 62 63 paper.unsubscribe(Jack.readInSunday,'monthly'); 64 paper.daily(); //Jack reads this is Olympic ! in the morning 65 //Amy reads this is Olympic ! in the morning 66 paper.monthly(); //Amy reads last month is the 28th Olympic! on Sunday 67