设计模式-发布订阅模式
emit:发布
on:订阅 先订阅再发布,改变顺序使用
埋点?
过度使用?
什么是发布订阅模式
家有喜事,当然要大家一起庆祝,顺便收份子钱,于是我打开了通讯录,向亲朋好友发布这个好消息。讲完了吗? 是的,哈哈哈,这就是发布订阅模式,我发布一个消息,订阅这个消息的人收到了通知。
写一个简单的发布订阅事件
js
yourMessage = {};
yourMessage.messageList = [];
yourMessage.on = function (fn) {
yourMessage.messageList.push(fn);
}
yourMessage.emit = function () {
for (var i = 0, fn; fn = yourMessage.messageList[i++];) {
fn.apply(this, arguments); //使用call 和 使用 bind 不一样
}
}
yourMessage.on(function (name) {
// console.log(Object.prototype.toString.call(name1))
console.log(`欢迎, ${name}`)
})
yourMessage.on(function () {
console.log('哈哈哈')
})
yourMessage.emit('花大姐', '马达话')
yourMessage.emit('勤大妈')
输出结果:
额外插播一点: 上面的代码里面说到,使用 call
和 使用 apply
是不一样的,上面的运行结果是使用apply
的运行结果,我们看一下使用call
的运行结果:
对于结果不一样,我说下自己的见解,apply
和 call
接收参数的形式不一样,apply
以数组的形式接收参数,call
接收的是一系列参数,arguments
是类数组类型,用call
去接收会把整个类数组传过去,用apply
接收传给function
,function
接收的时候可以以解构的方式去赋值给形参。
这个栗子中,只要订阅就会把所有消息发布一次,但是有时候订阅者并不想听到你所有的消息,这时候就需要改进一下。
改进一下上面的栗子
js
yourMessage = {};
yourMessage.messageList = {};
yourMessage.on = function (key, fn) {
if (!this.messageList[key]) this.messageList[key] = [];
this.messageList[key].push(fn);
}
yourMessage.emit = function () {
let key = Array.prototype.shift.call(arguments);
if (!this.messageList[key] || this.messageList[key].length <= 0) return;
for (var i = 0, fn; fn = this.messageList[key][i++];) {
fn.apply(this, arguments);
}
}
yourMessage.on('欢迎', function (name) {
console.log(`欢迎, ${name}`)
})
yourMessage.on('哈哈', function (name) {
console.log(`哈哈,${name}`)
})
yourMessage.emit('欢迎', '马达话')
yourMessage.emit('哈哈', '勤大妈')
再改进一下栗子
新增取消订阅和一次订阅
js
let eventEmitter = {
list: {},
//订阅
on(key, fn) {
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
},
//发布
emit() {
let key = Array.prototype.shift.call(arguments);
if (!this.list[key] || this.list[key].length <= 0) return;
for (let i = 0, fn; fn = this.list[key][i++];) {
fn.apply(this, arguments);
}
},
//一次订阅
once(key, fn) {
let on = function () { //一次性订阅只需要把传入的订阅事件包装一下
fn.apply(this, arguments);
this.off(key, on);
}
this.on(key, on);
},
//取消订阅
off(key, obj) {
if (!this.list[key] || this.list[key].length <= 0) return;
for (let i = 0, fn; fn = this.list[key][i]; i++) {
if (fn === obj) {
this.list[key].splice(i, 1);
break;
}
}
}
}
function fn1(content) {
console.log('One ', content);
}
function fn2(content) {
console.log('Two ', content)
}
eventEmitter.once('listen', fn1);
eventEmitter.once('listen', fn2);
eventEmitter.emit('listen', '哈哈哈')
//eventEmitter.off('listen', fn1);
eventEmitter.emit('listen', '哈哈哈')
关于发布订阅事件的顺序
前面实现的发布订阅都需要先订阅再发布,才可以收到消息,这次我们来实现一个可以支持先发布再订阅的发布订阅,也就是可以支持历史信息的订阅。
js
//改变发布订阅顺序
let eventEmitter = {
list: {},
cached: {},
//订阅
on(key, fn) {
if (!this.list[key]) {
this.list[key] = [];
}
if (this.list[key].indexOf(fn) != -1) return; //防止多次订阅
this.list[key].push(fn);
if (this.cached[key])
for (let i = 0, content; content = this.cached[key][i]; i++) {
fn.apply(this, content);
}
},
//发布
emit() {
let key = Array.prototype.shift.call(arguments);
if (!this.cached[key]) this.cached[key] = [];
this.cached[key].push(arguments);
if (!this.list[key] || this.list[key].length <= 0) return;
for (let i = 0, fn; fn = this.list[key][i++];) {
fn.apply(this, arguments);
}
},
//一次订阅
once(key, fn) {
let on = function () {
fn.apply(this, arguments);
this.off(key, on);
}
this.on(key, on);
},
//取消订阅
off(key, obj) {
if (!this.list[key] || this.list[key].length <= 0) return;
for (let i = 0, fn; fn = this.list[key][i]; i++) {
if (fn === obj) {
this.list[key].splice(i, 1);
break;
}
}
}
}
function fn1(content) {
console.log('One ', content);
}
function fn2(content) {
console.log('Two ', content)
}
eventEmitter.emit('listen', '哈哈哈')
eventEmitter.on('listen', fn1);
eventEmitter.on('listen', fn2);
//eventEmitter.off('listen', fn1);
//eventEmitter.emit('listen', '哈哈哈')
运行结果: