订阅-发布模式
amandakelake opened this issue · 0 comments
amandakelake commented
初学者都会用的订阅-发布模式
只要我们操作过DOM,都已经使用过订阅发布模式了,即使初学那会并不知道我们用了这种模式,看下这段代码:
document.getElementById("myId").addEventListener('click', () => {
//... do something
})
只要用户点击了id是myId
的DOM元素,就会自动执行注册的匿名函数
这里相当于订阅了这个DOM节点的click
事件,然后等待用户发布事件
当然,我们也可以随意的增加多个订阅者,或者删除订阅者
const ele = document.getElementById("myId");
ele.addEventListener('click', () => alert('hi'));
ele.addEventListener('click', () => alert('hello'));
ele.addEventListener('touchmove', () => alert('world'));
// 注意,通过 addEventListener() 来添加的匿名函数无法移除
ele.removeEventListener('click', handler);
订阅-发布的简单实现
除了常见的DOM事件,在日常业务开发中,我们经常会碰到需要实现自定义事件的场景,或者我们常用的vue框架也是利用订阅发布模式来实现的
下面我们一步步来实现一个简单的订阅发布模式,以下是核心步骤
- 创建一个全局对象作为发布者,发布者需要维护一个缓存列表
hub
,用于存放回调函数 - 实现订阅功能
on
,订阅事件,并注册回调函数,保存到发布者的缓存列表中 - 实现发布功能
emit
,发布事件,执行对应事件的回调函数 - 实现取消订阅功能
off
export const createEventHub = () => ({
// 生成一个空对象,该对象不继承Object.prototype的属性,比如toString这些它都不需要
// hub的格式: { [event1]: [handler1, handler2, handler3, ...], [event2]: [], [event3]: [], ...}
hub: Object.create(null),
// 订阅事件,并注册回调函数handler
on(event, handler) {
// 如果事件不存在,创建一个event空数组
if (!this.hub[event]) {
this.hub[event] = [];
}
this.hub[event].push(handler);
},
// 发布事件
emit(event, data) {
(this.hub[event] || []).forEach(handler => handler(data));
},
// 取消订阅
off(event, handler) {
// 寻找hub[event]里是否存在这个handler
const i = (this.hub[event] || []).findIndex(h => h === handler);
if (i > -1) {
// 如果存在,移除该事件所绑定的handler
this.hub[event].splice(i, 1);
}
}
})
使用方式
const eventHub = createEventHub();
let createEventHubVariable = 1;
const handler = data => {
console.log(data);
}
// hub订阅了myEvent, myEvent2 两个事件
// 其中myEvent事件注册了两个回调handler 和 匿名函数
eventHub.on('myEvent', handler);
eventHub.on('myEvent', () => {
console.log('I am handler 2');
console.log('eventHub', eventHub)
});
eventHub.on('myEvent2', () => {
createEventHubVariable++;
console.log('eventHub', eventHub);
});
// 某些用户或者数据变化产生的交互:发布
// 传入不同的参数,执行回调handler, 同时也会执行上面注册的匿名函数
eventHub.emit('myEvent', 'I am a string');
eventHub.emit('myEvent', { arg: 'I am a object'});
eventHub.emit('myEvent2');
// 只移除了handler, 匿名函数还是在的
eventHub.off('myEvent', handler)
Demo
下面的完整代码都可以在这里找到codepen: 订阅-发布模式, 自己随意改改代码就知道来龙去脉了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>FE-environment</title>
<style>
button {
background-color: #e6e6e6;
padding: 5px 10px;
border-radius: 2px;
margin: 5px 10px;
}
</style>
</head>
<body>
<h1>FE-environment</h1>
<div>发布订阅简易版本</div>
<div>
<button id="log-hub">log hub</button>
<button id="log-va">log变量</button>
</div>
<div>
<span>变量createEventHubVariable</span>
<span id="createEventHubVariable"></span>
</div>
<div>
<button id="subscribe-myEvent-h">订阅myEvent handler</button>
<button id="subscribe-myEvent-u">订阅myEvent 匿名函数</button>
<button id="subscribe-myEvent2">订阅myEvent2</button>
</div>
<div>
<button id="emit-myEvent">发布myEvent</button>
<button id="emit-myEvent2">发布myEvent2</button>
</div>
<div>
<button id="off-myEvent-handler">移除myEvent handler</button>
</div>
</body>
</html>
button {
background-color: #e6e6e6;
padding: 5px 10px;
border-radius: 2px;
margin: 5px 10px;
}
const createEventHub = () => ({
// 生成一个空对象,该对象不继承Object.prototype的属性,比如toString这些它都不需要
// hub的格式: { [event1]: [handler1, handler2, handler3, ...], [event2]: [], [event3]: [], ...}
hub: Object.create(null),
// 订阅事件,并注册回调函数handler
on(event, handler) {
// 如果事件不存在,创建一个event空数组
if (!this.hub[event]) {
this.hub[event] = [];
}
this.hub[event].push(handler);
},
// 发布事件
emit(event, data) {
(this.hub[event] || []).forEach(handler => handler(data));
},
// 取消订阅
off(event, handler) {
// 寻找hub[event]里是否存在这个handler
const i = (this.hub[event] || []).findIndex(h => h === handler);
if (i > -1) {
// 如果存在,移除该事件所绑定的handler
this.hub[event].splice(i, 1);
}
}
})
const eventHub = createEventHub();
let createEventHubVariable = 1;
document.getElementById("createEventHubVariable").innerHTML = createEventHubVariable;
const handler = data => {
console.log(data);
}
/*
// hub订阅了myEvent, myEvent2 两个事件
// 其中myEvent事件注册了两个回调handler 和 匿名函数
eventHub.on('myEvent', handler);
eventHub.on('myEvent', () => {
console.log('I am handler 2');
console.log('eventHub', eventHub)
});
eventHub.on('myEvent2', () => {
createEventHubVariable++;
console.log('eventHub', eventHub);
});
// 某些用户或者数据变化产生的交互:发布
// 传入不同的参数,执行回调handler, 同时也会执行上面注册的匿名函数
eventHub.emit('myEvent', 'I am a string');
eventHub.emit('myEvent', { arg: 'I am a object'});
eventHub.emit('myEvent2');
// 只移除了handler, 匿名函数还是在的
eventHub.off('myEvent', handler)
*/
document.getElementById("log-hub").addEventListener('click', () => {
console.log('eventHub', eventHub);
})
document.getElementById("log-va").addEventListener('click', () => {
console.log('createEventHubVariable', createEventHubVariable);
})
document.getElementById("subscribe-myEvent-h").addEventListener('click', () => {
eventHub.on('myEvent', handler);
console.log('eventHub', eventHub);
})
document.getElementById("subscribe-myEvent-u").addEventListener('click', () => {
eventHub.on('myEvent', () => console.log('I am handler 2'));
console.log('eventHub', eventHub)
})
document.getElementById("subscribe-myEvent2").addEventListener('click', () => {
eventHub.on('myEvent2', () => {
createEventHubVariable++;
document.getElementById("createEventHubVariable").innerHTML = createEventHubVariable;
});
console.log('eventHub', eventHub)
})
document.getElementById("emit-myEvent").addEventListener('click', () => {
eventHub.emit('myEvent', 'I am a string');
eventHub.emit('myEvent', {
arg: 'I am a object'
});
})
document.getElementById("emit-myEvent2").addEventListener('click', () => {
eventHub.emit('myEvent2');
document.getElementById("createEventHubVariable").innerHTML = createEventHubVariable;
console.log('createEventHubVariable', createEventHubVariable);
})
document.getElementById("off-myEvent-handler").addEventListener('click', () => {
eventHub.off('myEvent', handler);
console.log('eventHub', eventHub);
})