之前的总结:设计模式之发布订阅模式
为什么会有这种设计模式
这里有个很好的回答:https://segmentfault.com/q/1010000002487388
简单的基于对象的订阅发布模式
function EventEmitter() {
this._event = {}
}
EventEmitter.prototype.on= function (type, handle) {
this._event[type] = this._event[type] || []
this._event[type].push(handle)
}
EventEmitter.prototype.remove = function (type, handle) {
var index = (this._event[type] || []).indexOf(handle)
if(index !== -1) {
this._event[type].splice(index, 1)
}
}
EventEmitter.prototype.emit = function (type, data) {
(this._event[type] || []).forEach(function (handle) {
handle(data)
})
}
var test = new EventEmitter();
var handle1 = function (data) {
console.log(data[0])
}
var handle2 = function () {
console.log(data[1])
}
test.on('fetchData', handle1)
test.on('fetchData', handle2)
test.remove('fetchData', handle2)
test.emit('fetchData', [1,2,3])
// 1
常见的使用场景:当我们在ajax的异步数据请求结束后,emit一个事件,外部可以通过监听这个事件执行不同的操作
原生web api基于DOM元素的发布订阅
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="ele"></div>
<script>
// 基于对象的订阅/发布
// 基于DOM元素的事件订阅/发布
var event = new CustomEvent('add', {
detail: 'hello'
});
var handle = function (e) {
console.log('handle:' + e.detail);
}
var handle2 = function (e) {
console.log('handle2:' + e.detail);
}
ele.addEventListener('add', handle)
ele.addEventListener('add', handle2)
ele.dispatchEvent(event); // handle:hello handle2:hello
ele.removeEventListener('add', handle2);
ele.dispatchEvent(event); // handle:hello
</script>
</body>
</html>
使用ES6实现的发布订阅
http://zchange.cn/posts/332959902.html
class Event {
constructor() {
this._subscribers = new Map();
this.__index = 0;
}
/**
* 将订阅者信息存入list
* @param {String} eventName 事件名称
* @param {fn} callback 订阅回调
* 通过Map来存取对应的订阅者
* 监听同一个主体,下一次的不会覆盖上一次的监听
* 返回订阅者名称,和对应的下标,可供后面销毁
*/
subscribe(eventName, callback) {
if (typeof eventName !== 'string' || typeof callback !== 'function') {
throw new Error('parameter error')
}
if (!this._subscribers.has(eventName)) {
this._subscribers.set(eventName,new Map());
}
// 订阅同一个主题通过_index不会覆盖上一次。
this._subscribers.get(eventName).set(++this._index,callback);
return [eventName, this._index]
}
on(eventName, callback) {
return this.subscribe(eventName, callback);
}
/**
* 发布信息
* @param {String} eventName 订阅者名称
* @param {any} args 参数
*/
emit(eventName, ...args) {
if(this._subscribers.has(eventName)){
const eveMap = this._subscribers.get(eventName);
eveMap.forEach((map) =>map(...args));
}else{
throw new Error(`The subscription parameter ${eventName} does not exist`)
}
}
/**
* 销毁对应订阅者
* @param {String|Object} event
*/
destroy(event) {
if (typeof event === 'string') {
// 直接销毁对应订阅者
if (this._subscribers.has(event)) {
this._subscribers.delete(event)
}
} else if (typeof event === 'object') {
// 通过订阅者名词和下标,销毁其中某一个订阅者
const [eventName, key] = event;
this._subscribers.get(eventName).delete(key);
}
}
/**
* 清除所有订阅者
*/
remove() {
this._subscribers.clear();
}
}
const $event = new Event();
const ev1 = $event.on('aa', (...agrs) => {
console.log(...agrs);
console.log(111);
})
const ev2 = $event.on('aa', (...agrs) => {
console.log(...agrs);
console.log(222);
})
setTimeout(() => {
$event.emit('aa', '1', '2');
$event.destroy();
$event.remove();
}, 500)
原生web api实现依赖于dom元素,即发布者和订阅者都是dom元素,因此使用场景有限。基于对象的实现方式常用于跨组件之间的数据传递,可以更好的解耦发布者和订阅者之间的联系。