Publish/Subscribe 实现
Publish / Subscribe非常适合JavaScript生态系统,主要原因在于ECMAScript实现的核心是事件驱动。在浏览器环境中尤其如此,因为DOM使用事件作为脚本编写的主要交互API。
也就是说,ECMAScript和DOM都不会在实现代码中提供用于创建自定义事件系统的核心对象或方法(除了可能与DOM绑定的DOM3 CustomEvent,因此通常不会有用)。
幸运的是,流行的JavaScript库如dojo,jQuery(自定义事件)和YUI已经有了可以帮助轻松实现发布/订阅系统的工具。下面我们可以看到一些这样的例子:
js
// 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')为了让我们能够理解观察者模式中有多少种JavaScript实现可以工作,让我们来看看在pubsubz下的一个项目中,我发布在GitHub上的最低版本的Publish / Subscribe。这展示了订阅,发布以及取消订阅概念的核心概念。
我选择以这些代码为基础的示例,因为它密切关注方法签名和实现方法,我期望在经典Observer模式的JavaScript版本中看到这些方法。
原生实现
js
var pubsub = {}
;(function(myObject) {
// 存储或监听可广播的主题
var topic = {}
// 主题标识
var subUid = -1
// 发布或广播感兴趣的事件, 具有特定的主题名称和参数, 例如要传递的数据
myObject.publish = function(topic, args) {
if (!topics[topic]) return false
var subscribers = topics[topic]
var len = subscribers ? subscribers.length : 0
while (len--) {
subscribers[len].func(topic, args)
}
return this
}
//订阅感兴趣的活动, 使用特定的主题名称和回调函数,执行观察话题/事件
myObject.subsribe = function(topic, func) {
if (!topics[topic]) {
topics[topic] = []
}
var token = (++subUid).toString()
topics[topic].push({
token: token,
func: func
})
return token
}
// 取消订阅特定的主题,基于标记化引用订阅
myObject.unsubscribe = function(token) {
for (var m in topics) {
if (topics[m]) {
for (var i = 0, j = topics[m].length; i < j; i++) {
if (topics[m][i].token === token) {
topics[m].splice(i, 1)
return token
}
}
}
}
return this
}
})(pubsub)使用以上的实现
我们现在可以使用实现来发布和订阅感兴趣的事件,如下所示:
js
// 另一个简单的消息处理
// 一个简单的消息记录器,记录通过我们收到的任何主题和数据用户
var messageLogger = function(topics, data) {
console.log('Logging: ' + topics + ': ' + data)
}
// 订阅者收听他们订阅的主题, 调用一个新的回调函数(例如messageLogger)
// 通知在该主题上广播
var subscription = pubsub.subscripbe('inbox/newMessage', messageLogger)
// 发布者负责发布主题或通知对应用程序的兴趣。例如:
pubsub.publish('inbox/newMessage', 'hello world')
// or
pubsub.publish('inbox/newMessage', ['test', 'a', 'b', 'c'])
// or
pubsub.publish('index/newMessage', {
sender: 'hello@google.com',
body: 'hey again!'
})
// 如果我们不再希望订阅者,我们也可以退订,将被通知
pubsub.unsubscribe(subscription)
// 一旦取消订阅,再发布也不会生效
pubsub.publish('inbox/newMessage', 'hello, are you still there ?')