# 发布订阅模式
发布订阅模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都将收到通知。 在JavaScript中,我们一般用事件模型(注册回调函数的形式)来替代传统的发布订阅模式
DOM就是一个最常见的发布订阅
// 1. 多个订阅者
function fn1() {
console.log('订阅者111')
}
function fn2() {
console.log('订阅者222')
}
document.addEventListener('click', fn1)
document.addEventListener('click', fn2)
// console.log('订阅者111')
// console.log('订阅者222')
// 2. 订阅可取消
document.addEventListener('click', fn1)
document.addEventListener('click', fn2)
document.removeEventListener('click', fn2)
// console.log('订阅者111')
再以公众号的消息机制来举例
// 公众号对象
let eventEmitter = {};
// 缓存列表,存放 event 及 fn
eventEmitter.list = {};
// 订阅
eventEmitter.on = function (event, fn) {
let _this = this;
// 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
// 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
(_this.list[event] || (_this.list[event] = [])).push(fn);
return _this;
};
// 发布
eventEmitter.emit = function () {
let _this = this;
// 第一个参数是对应的 event 值,直接用数组的 shift 方法取出
let event = [].shift.call(arguments),
fns = [..._this.list[event]];
// 如果缓存列表里没有 fn 就返回 false
if (!fns || fns.length === 0) {
return false;
}
// 遍历 event 值对应的缓存列表,依次执行 fn
fns.forEach(fn => {
fn.apply(_this, arguments);
});
return _this;
};
function user1 (content) {
console.log('用户1订阅了:', content);
};
function user2 (content) {
console.log('用户2订阅了:', content);
};
// 订阅
eventEmitter.on('article', user1);
eventEmitter.on('article', user2);
// 发布
eventEmitter.emit('article', 'Javascript 发布-订阅模式');
完善 once 和 off 方法
let eventEmitter = {
// 缓存列表
list: {},
// 订阅
on (event, fn) {
let _this = this;
// 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
// 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
(_this.list[event] || (_this.list[event] = [])).push(fn);
return _this;
},
// 监听一次
once (event, fn) {
// 先绑定,调用后删除
let _this = this;
function on () {
_this.off(event, on);
fn.apply(_this, arguments);
}
on.fn = fn;
_this.on(event, on);
return _this;
},
// 取消订阅
off (event, fn) {
let _this = this;
let fns = _this.list[event];
// 如果缓存列表中没有相应的 fn,返回false
if (!fns) return false;
if (!fn) {
// 如果没有传 fn 的话,就会将 event 值对应缓存列表中的 fn 都清空
fns && (fns.length = 0);
} else {
// 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可
let cb;
for (let i = 0, cbLen = fns.length; i < cbLen; i++) {
cb = fns[i];
if (cb === fn || cb.fn === fn) {
fns.splice(i, 1);
break
}
}
}
return _this;
},
// 发布
emit () {
let _this = this;
// 第一个参数是对应的 event 值,直接用数组的 shift 方法取出
let event = [].shift.call(arguments),
fns = [..._this.list[event]];
// 如果缓存列表里没有 fn 就返回 false
if (!fns || fns.length === 0) {
return false;
}
// 遍历 event 值对应的缓存列表,依次执行 fn
fns.forEach(fn => {
fn.apply(_this, arguments);
});
return _this;
}
};
function user1 (content) {
console.log('用户1订阅了:', content);
}
function user2 (content) {
console.log('用户2订阅了:', content);
}
function user3 (content) {
console.log('用户3订阅了:', content);
}
function user4 (content) {
console.log('用户4订阅了:', content);
}
// 订阅
eventEmitter.on('article1', user1);
eventEmitter.on('article1', user2);
eventEmitter.on('article1', user3);
// 取消user2方法的订阅
eventEmitter.off('article1', user2);
eventEmitter.once('article2', user4)
// 发布
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');
总结
- 优点
- 对象之间解耦
- 异步编程中,可以更松耦合的代码编写
- 缺点
- 创建订阅者本身要消耗一定的时间和内存
- 虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护