🤔 废话不多说,在实现Event类之前,让我们先了解一下什么是观察者模式
观察者模式
一, 定义
观察者模式(Observer Pattern)是一种常用的软件设计模式,用于构建对象之间的一对多依赖关系。在这种模式中,当一个对象(被观察者)的状态发生变化时,它会自动通知其他依赖于它的对象(观察者),使它们能够及时做出响应。观察者模式也叫 发布-订阅(Publish/Subscribe)模式,观察者模式是一种对象行为模式,是不是有点云里雾里,下面会举个生活中的小例子做解释
二,观察者模式涉及以下几个角色
-
被观察者(Subject):也称为主题或发布者,在该模式中,它维护了一组观察者对象,并提供注册、注销以及通知观察者的方法。当被观察者状态发生变化时,它会遍历观察者列表,调用每个观察者的更新方法。
-
观察者(Observer):也称为订阅者或监听者,观察者定义了一个更新方法,在接收到被观察者的通知时会调用该方法来执行相应的操作。观察者可以注册到一个或多个被观察者中。
二,模式特点
-
解偶性:被观察者和观察者之间通过接口进行通信,彼此之间解偶,可以独立修改和扩展。
-
易于扩展:可以在不修改被观察者的情况下增加新的观察者。
-
支持广播通信:被观察者可以同时通知多个观察者,实现一对多的依赖关系。
-
观察者模式符合'关闭原则'的要求
三,模式缺点
-
目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
-
当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
四,生活中的观察者模式
耐克这几年出了很多爆款,小明看中了其中某一款鞋,于是去耐克店购买,但是到了之后,售货员告诉他今天断货了,可是他又不想每天都跑过来问,于是就把自己的电话留给了店员小姐姐,等到有货的时候就让小姐姐打电话通知他,等到他拿到货之后这件事也算完成了,那么小姐姐就不需要在给他打电话了
在这个例子中,小明是观察者,而售货员小姐姐是被观察者。
小明希望得到关于鞋子货物状态的通知,因此他把自己的电话留给了售货员小姐姐。这使得小姐姐成为被观察者,因为她需要在货物到货时通知小明。
售货员小姐姐在这种情况下充当被观察者的角色,因为她需要在鞋子有货时通知观察者(小明)。她会监测货物的到货情况,并负责与小明进行沟通,以确保他及时获取到所需的货物。
观察者模式的使用
js
let el = doucument.querySelector('#box')
el.addEventListener('mousedown', (e) => {
console.log(e)
})
我们常见的给dom元素绑定事件,就是发布订阅模式,我们想要知道鼠标按下的行为,但不知道用户什么时候按下,所以我们订阅这个mousedown事件,当鼠标被按下时就会向订阅者发布消息,这时我们就可以做对应的操作
创建一个观察者对象
首先我们创建一个观察者对象,它包含一个消息容器和三个方法,分别为订阅消息方法on,移除订阅消息方法off,发送订阅消息dispatch 通过定义一个event类来实现
js
class event {
events = {} // 事件池也就是消息容器,用来记录所有相关事件及处理函数
on() {} // 注册消息接口
off() {} // 取消订阅接口
dispatch() {} // 触发接口
}
这样一个观察者对象雏形就出来了
创建一个事件池
js
events = {}
/* eg:
events = {
"click":[f1,f2,f3......],
"mousemove": [f1,f2,f3......]
}
*/
注册消息
注册消息的目的是将订阅者注册的消息推入到消息队列中,需要两个参数:消息类型和对应的处理函数,在推入消息之前需要先判断队列中是否存在此消息
js
on(eventName, fn) { // 添加一个事件处理 eventName事件名,fn 方法
if(!this.events[eventName]) {
// 如果此消息不存在创建一个该消息类型
this.events[eventName] = [];
};
if(!this.events[eventName].includes(fn)) {
// 将执行方法推入到该消息对应的执行队列中
this.events[eventName].push(fn);
};
}
发布订阅消息
其功能就是将所有订阅者订阅的消息依次执行,同样需要两个参数,分别是消息类型,和对应执行函数所需的参数
js
dispatch(eventName, ...arg) {
if(!this.events[eventName]) { // 如果没有添加过改消息类型则直接跳出
return ;
}
this.events[eventName].forEach(item => {
item.call(this, ...arg);
})
}
移除订阅消息
移除订阅消息方法的作用是将订阅者注销的消息从队列中清除掉,因为需要知道要移除的消息类型和执行方法,所以也需要两个参数
js
off(eventName, fn) {
// 需要校验消息是否存在
if(!this.events[eventName]) {
return ; // 直接跳出
}
this.events[eventName] = this.events[eventName].filter(item => item !== fn)
}
好了,到此,我们基于观察者模式实现了一个基本的Event类, 全部代码如下
js
class Event {
// 创建一个事件池记录所有的相关事件及处理函数
events = {}
// 添加一个事件处理 eventName事件名,fn 方法
on(eventName, fn){
// 如果不存在这个事件 直接跳出
if(!this.events[eventName]){
this.events[eventName] = [];
}
// 验重
if(!this.events[eventName].includes(fn)) {
this.events[eventName].push(fn);
}
}
// 添加一个删除事件
off(eventName, fn){
if(!this.events[eventName]){
return;
}
this.events[eventName] = this.events[eventName].filter(item => item !== fn)
}
// 负责把触发到的事件给执行了
dispatch(eventName, ...arg) {
// 如果不存在这个事件 直接跳出
if(!this.events[eventName]){
return;
}
this.events[eventName].forEach(item => {
item.call(this, ...arg)
})
}
}
做一些简单的测试看看效果如何
js
let event = new Event();
event.on('drag', () => {
console.log('你想怎样拖拽')
})
event.on('drag', () => {
console.log('随便拖拽')
})
event.dispatch('drag')
控制台可以看到打印结果如下
取消消息订阅会如何呢
js
let event = new Event();
let f = () => {
console.log('我会被移除')
}
event.on('drag', f)
event.off('drag', f)
event.dispatch('drag')
执行结果如下 我们发现添加在drag消息类型中的执行方法全部被清理掉了
到此我们成功的基于观察者模式实现了一个Event类,后期我们会基于这个类实现一些特殊的拖拽场景,
敬请期待!!!!!