聊聊发布-订阅设计模式
发布于 2021-01-15 23:20
let eventsMixin = {
events: {},
on(event, fn) {
(this.events[event] || (this.events[event] = [])).push(fn);
},
/* 发布数据 */
emit(event, content) {
if (!this.events[event] || this.events[event].length === 0) {
return;
}
let tempList = this.events[event];
tempList.forEach(fn => {
fn(content)
})
}
}
eventsMixin.on('handleTitle', handleTitle);
eventsMixin.emit('handleTitle', '标题');
/* 处理事件 */
function handleTitle(title) {
console.log('收到消息了--->', title);
}
eventsMixin.on('handleTitle', handleTitle);
eventsMixin.on('content', handleContent);
eventsMixin.on('content', handleContent1);
eventsMixin.emit('handleTitle', '标题');
eventsMixin.emit('content', '内容');
function handleTitle(title) {
console.log('收到消息了--->', title);
}
function handleContent(content) {
console.log('收到消息了--->', content);
}
function handleContent1(content) {
console.log('收到消息了1--->', content);
}
off(event, fn) {
/* 需要校验参数 */
let _this = this;
// let fns = _this.events[event];
/* 没有参数,移除所有的事件监听器 */
if (!arguments.length) {
_this.events = {};
return;
}
/* 只有事件名称,移除该事件所有的监听器 */
if (arguments.length === 1) {
delete _this.events[event];
return;
}
/* 如果同时提供了事件与回调,则只移除这个回调的监听器 */
if (arguments.length === 2) {
let fnList = _this.events[event];
if (!fnList) return;
// 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可
for (let i = 0; i < fnList.length; i++) {
if (fnList[i] === fn) {
fnList.splice(i, 1);
break
}
}
}
}
eventsMixin.on('handleTitle', handleTitle);
eventsMixin.on('content', handleContent);
eventsMixin.on('content', handleContent1);
eventsMixin.emit('handleTitle', '标题');
eventsMixin.emit('content', '内容');
eventsMixin.off('content', handleContent);
eventsMixin.emit('content', '新的内容');
/* 监听一次 函数在eventsMixin对象中,简写了*/
once(event, fn) {
let _this = this;
// 先绑定,调用后删除
// console.log(fn);
function on() {
_this.off(event, on);
fn.apply(_this, arguments);
}
on.fn = fn;
_this.on(event, on);
},
eventsMixin.once('content', handleContent);
eventsMixin.emit('content', '内容');
eventsMixin.emit('content', '新的内容');
once函数里面还定义了一个on函数(姑且叫内部on函数),内部on函数中执行off方法,和fn函数(姑且叫内部fn函数)。内部fn函数的绑定了once方法中的形参fn。最后执行on函数(外部的on函数)。这里超级绕,有一个办法可以理清楚,那就是打断点调试,就可以看到函数的执行过程了。
最终结果也符合我们的预期。
难道到这里就结束了吗?并没有,下面我想分享的才是我遇到的坑和网上分享不同的地方。拿起小本本仔细听。
eventsMixin.once('content', handleContent);
eventsMixin.once('content', handleContent1);
eventsMixin.emit('content', '内容');
我看了很多遍代码后,也没有看出什么毛病,所以我就试着打断点去调试,发现当我们去做删除的时候eventList数组发生了变化,我们再在emit函数中进行forEach循环,那么就得不到数组中第二个函数了,它现在变成第一个了。那要怎么解决,好办,反向遍历即可。
let i = eventList.length;
while (i--) {
eventList[i](content);
}
得到数据了。所以遍历的时候去删除数组中的数据就要注意了,这一点,我在java遍历数组并删除数据遇到的问题也做过相应的笔记。当时使用的是迭代器。es6中也引入了迭代器的概念,那么js的迭代器也可以实现(不过我还没试,你可以试试)。
集合与泛型第三篇
在vue2源码中也能看到这样的处理(我当然是抄袭的了)
Vue.prototype.$off = function (event, fn) {
var vm = this;
// all
if (!arguments.length) {
vm._events = Object.create(null);
return vm
}
// array of events
if (Array.isArray(event)) {
for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
vm.$off(event[i$1], fn);
}
return vm
}
// specific event
var cbs = vm._events[event];
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null;
return vm
}
// specific handler
var cb;
var i = cbs.length;
while (i--) {
cb = cbs[i];
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break
}
}
return vm
};
只不过源码里面是写在off函数里的。原理还是一样倒过来遍历数组
下面是完整的代码
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
/* 定义调度中心 */
let eventsMixin = {
events: {},
on(event, fn) {
(this.events[event] || (this.events[event] = [])).push(fn);
},
/* 发布数据 */
emit(event, content) {
if (!this.events[event] || this.events[event].length === 0) {
console.log(, event, content);
return;
}
let eventList = this.events[event];
// eventList.forEach(fn => {
// fn(content)
// })
// for (let i in eventList) {
// eventList[i](content);
// }
// for (let i = 0; i < eventList.length; i++) {
// eventList[i](content);
// }
let i = eventList.length;
while (i--) {
eventList[i](content);
}
// for (let item of eventList) {
// item(content);
// }
},
/* 监听一次 */
once(event, fn) {
let _this = this;
// 先绑定,调用后删除
// console.log(fn);
function on() {
// console.log(fn);
_this.off(event, on);
fn.apply(_this, arguments);
}
on.fn = fn;
_this.on(event, on);
},
off(event, fn) {
/* 需要校验参数 */
let _this = this;
// let fns = _this.events[event];
/* 没有参数,移除所有的事件监听器 */
if (!arguments.length) {
_this.events = {};
return;
}
/* 只有事件名称,移除该事件所有的监听器 */
if (arguments.length === 1) {
delete _this.events[event];
return;
}
/* 如果同时提供了事件与回调,则只移除这个回调的监听器 */
if (arguments.length === 2) {
let fnList = _this.events[event];
if (!fnList) return;
// 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可
for (let i = 0; i < fnList.length; i++) {
if (fnList[i] === fn || fnList[i].fn === fn) {
fnList.splice(i, 1);
// console.log(fnList);
break
}
}
// let i = fnList.length
// while (i--) {
// cb = fnList[i];
// if (cb === fn || cb.fn === fn) {
// fnList.splice(i, 1);
// break
// }
// }
}
}
}
// eventsMixin.once('handleTitle', handleTitle);
eventsMixin.once('content', handleContent);
eventsMixin.once('content', handleContent1);
// eventsMixin.once('content', handleContent2);
// eventsMixin.once('content', handleTitle5);
// eventsMixin.emit('handleTitle', '标题');
// eventsMixin.emit('content', '内容');
eventsMixin.emit('content', '内容');
// eventsMixin.off('content', handleContent);
// eventsMixin.off();
// eventsMixin.emit('content', '新的内容');
// eventsMixin.on('content', handleTitle);
// eventsMixin.emit('content', '内容');
// eventsMixin.off();
/* 处理事件 */
function handleTitle(title) {
console.log('收到消息了--->', title);
}
function handleContent(content) {
console.log('收到消息了--->', content);
}
function handleContent1(content) {
console.log('收到消息了1--->', content);
}
function handleContent2(content) {
console.log('收到消息了2--->', content);
}
function handleTitle5(title) {
console.log('收到消息了55--->', title);
}
</script>
</body>
</html>
本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。
相关素材