书里的第八章「发布—订阅模式」的目录结构为:
- 第8章 发布—订阅模式
- 8.1 现实中的发布—订阅模式
- 8.2 发布—订阅模式的作用
- 8.3 DOM 事件
- 8.4 自定义事件
- 8.5 发布—订阅模式的通用实现
- 8.6 取消订阅的事件
- 8.7 真实的例子——网站登录
- 8.8 全局的发布—订阅对象
- 8.9 模块间通信
- 8.10 必须先订阅再发布吗
- 8.11 全局事件的命名冲突
- 8.12 JavaScript实现发布—订阅模式的便利性
- 8.13 小结
但直到了 *8.8 全局的发布—订阅对象*,前后的 code example 实际上是两种不同的对语观察者模式的实现:
- 8.8 之前是类似 Event Emit 的实现,
- 8.8 之后,从先订阅后发布的离线事件,到作为全局变量存在 global 对象,则是类似 Global Boardcast 的 Pub/Sub 的实现。
1. 类似 Event Emit 的实现:
这种实现方式尤其醒目的一点是,会有一个 Events 对象存在,而之后的每个对象都是从这个对象进行继承或者扩展属性和方法,也就是书里作者所使用的 installEvent
方法,而在每个对象之后的方法调用上,基本的形式会是:
myObject.on('EventName', handler);
myObject.tigger('EventName', param1, param2, ...);
myObject.off('EventName', handler);
jQuery 类似的 API 例子:
// Publish
// jQuery: $(obj).trigger("channel", [arg1, arg2, arg3]);
$( el ).trigger( "/login", [{username:"test", userData:"test"}] );
// Subscribe
// jQuery: $(obj).on( "channel", [data], fn );
$( el ).on( "/login", function( event ){...} );
// Unsubscribe
// jQuery: $(obj).off( "channel" );
$( el ).off( "/login" );
做了一个很简单的实现 ,只有 publish()
和 subscribe()
两个方法,利用 extend()
,将 publish()
和 subscribe()
方法扩展到新申明的对象上。只是一般实现,一般还会有 off()
和 once()
等方法。
function PubSub () {
this.events = {};
}
PubSub.prototype.subscribe = function (eventName, handler) {
(this.events[eventName] = this.events[eventName] || [])
.push(handler);
};
PubSub.prototype.publish = function (eventName) {
for (var list = this.events[eventName], i=0; list && list[i];) {
list[i++].apply(this, list.splice.call(arguments, 1));
}
};
function extend (obj, extension) {
for (var key in extension) {
obj[key] = extension[key];
}
return obj;
}
var extended = extend({}, new PubSub());
extended.subscribe('customEvent1', function (a,b) {
console.log(a+b);
});
extended.subscribe('customEvent2', function (a,b) {
console.log(a-b);
});
extended.publish('customEvent1', 1, 2); // 3
extended.publish('customEvent2', 1, 2); // 2
(new PubSub()).publish('customEvent1', 1, 2);
// nothing happened
2. 另一种是作为一个 Global Boardcast 的 Pub/Sub 的实现:
也就是书里最后 demo 的实现;不过书里作者的比一般的实现要复杂些,例如缓存消息的 offlineStack
,可能是讲一些平时遇到的业务需求的例子也加了进去;基本的形式会是:
globalBroadcaster.listen('EventName', handler);
globalBroadcaster.trigger('EventName', param1, param2, ...);
globalBroadcaster.remove('EventName', handler);
var Pubsub = {};
(function (obj) {
var events = {};
obj.subscribe = function (eventName, handler) {
(events[eventName] = events[eventName] || []).push(handler);
};
obj.publish = function (eventName) {
for (var list = events[eventName], i=0; list && list[i];) {
list[i++].apply(this, list.splice.call(arguments, 1));
}
};
}(Pubsub));
Pubsub.subscribe('customEvent1', function (a,b) {
console.log(a+b);
});
Pubsub.subscribe('customEvent2', function (a,b) {
console.log(a-b);
});
Pubsub.publish('customEvent1', 1, 2); // 3
Pubsub.publish('customEvent2', 1, 2); // 2
其实和上面 event emit 的实现上并没有多大区别,直接拿上面的下来也可以,只是在这里的使用方法不会用到 extend()
方法来对新申明的对象做属性和方法的扩展,Pubsub
在这里作为一个全局对象存在,一切消息都走 Global Boardcast。
Event Emit 和 Pub/Sub 的主要区别还是在对象方法的调用上,别的其实都很像,更加详细可以参考 Addy Osmani 写的《JavaScript Design Pattern》中的 The Observer Pattern 一章,到达这一章往下拉没多少就是 Pub/Sub 的实现。
最后要说的是,8.13 那里,作者说
本章我们学习了发布-订阅模式,也就是常说的观察者模式。
这其实不太对,Pub/Sub 很好的帮助了在日常开发中的对象之间的节藕,比如自己制作手势库,用到了 Pub/Sub,将系统原生的各种 touch 事件的 event 对象当作参数传送给自定义的处理收视判断的函数。两个对象之间并没有之间的关系,使用一个 Global Boardcast 的方式相互传递各自需要的数据。但这不是观察者模式。
按照 Addy Osmani 写的《JavaScript Design Pattern》中的 The Observer Pattern 一章 中关于观察者模式给出的,做了一个简单的例子 ;主要修改了作为 Observer 的 update() 方法。
观察者模式中的 Subject 和 Observer 主要相互关系在于,Subject 可以去 add() 或者 remove() 一个 Observer,需要的时候去使用 notify() 方法,去调用 Observer 对象上的 update() 方法。,而不是上文中实现的 pub/sub。
//Observers
var Observers = function () {
this.observers = [];
};
Observers.prototype.add = function (observer) {
this.observers.push(observer);
};
Observers.prototype.remove = function (observerToRemove) {
this.observers = this.observers.filter(function (observer) {
return observer !== observerToRemove;
});
};
Observers.prototype.get = function () {
return this.observers;
};
// Observer
var Observer = function (name) {
this.name = name;
};
Observer.prototype.update = function (event, data) {
console.log('the event was: ', event, ' the data was ', data, ' and name is ', this.name);
};
var Subject = function () {
this.observers = new Observers();
}
Subject.prototype.observer = function (observer) {
this.observers.add(observer);
};
Subject.prototype.unObserver = function (observer) {
this.observers.remove(observer);
};
Subject.prototype.notify = function (event, data) {
this.observers.get().forEach(function (observer) {
observer.update(event, data);
});
};
逻辑代码
var collection = extend(new Collection(), new Subject());
var observer = new Observer('observer 1');
var otherObserver = new Observer('observer 2');
collection.observer(observer);
collection.observer(otherObserver);
collection.notify('add', {prop: 'something'});
// the event was: add the data was Object {prop: "something"} and name is observer 1
// the event was: add the data was Object {prop: "something"} and name is observer 2
最后
第八章最后一章节的例子,包含离线消息接受和 namespace 实现的全局 Event 对象,原文中大段大段的代码没有注释(我看的是 kindle 的电子版,不知纸质的咋样),所以给加了一遍注释,还删掉些完全没有用到过但声明了的变量。
技术发展迭代很快,所以这些笔记内容也有类似新闻的时效性,不免有过时、或者错误的地方,欢迎指正 ^_^。
BEST
Lien(A.K.A 胡椒)