js设计模式系列篇:观察者模式
定义
观察者(observer)模式是js中应用很广泛的一种设计模式。它主要是通过在一个目标对象上维护一系列依附于它的观察者(observer)对象来实现,在目标对象上有任何状态改变时,会自动地通知给观察者。
先来看一个简单的实现
观察者的实现
class Observer { constructor () {} update (context) { //观察者收到通知后触发的函数 console.log(`recieved data is ${context}`) } } 复制代码
以上是简单的观察者的定义。目标对象在需要的时候可以触发update函数以实现对观察者的通知。
观察者列表
//观察者列表 class ObserverList { constructor () { //用数组去保存观察者 this.list = [] } //添加一个观察者 add (observer) { this.list.push(observer) } //获取观察者的个数 count () { return this.list.length } //移除观察者 remove (observer) { this.list = this.list.filter(item => item !== observer) } //获取观察者 getIndexAt (index) { if (index > -1 && index < this.count()) { return this.list[index] } } } 复制代码
以上是观察者列表,list数组中保存的是一个个Observer类的实例,即观察者。我们可以用该类,对依附在某一个目标对象上的观察者进行统一的管理和维护。
目标对象
//目标对象 class Subject { constructor () { //观察者依附于目标对象上 this.observers = new ObserverList() } //在目标对象上新增一个观察者 addObserver (observer) { this.observers.add(observer) } //从目标对象上移除观察者 removeObserver (observer) { this.observers.remove(observer) } //通知观察者 notify (context) { const len = this.observers.count() for (let i = 0; i < len; i++) { this.observers.getIndexAt(i).update(context) } } } 复制代码
从以上代码可以看到,目标对象实例上挂载了一个ObserverList实例对象。用来保存依附在该目标对象上的所有观察者。当目标对象状态有变化时,可以通过notify函数,遍历触发所有观察者上的update函数,以此来实现对所有观察者的通知
代码实现完毕,我们现在来试验一下
//创建观察者 const observer1 = new Observer() const observer2 = new Observer() //创建一个目标对象 const subject = new Subject() //将两个观察者加入到目标对象上的观察者列表中 subject.addObserver(observer1) subject.addObserver(observer2) //通知观察者 subject.notify('hello my observer') 复制代码
运行以上代码,可以在控制台看到输出了两次,如下图
这就说明目标对象成功通知到了依附在自身的两个观察者。
我们来试验一下移除观察者功能
//创建观察者 const observer1 = new Observer() const observer2 = new Observer() //创建一个目标对象 const subject = new Subject() //将两个观察者加入到目标对象上的观察者列表中 subject.addObserver(observer1) subject.addObserver(observer2) //移除观察者 subject.removeObserver(observer1) //通知观察者 subject.notify('hello my observer') 复制代码
以上代码中,目标对象在通知观察者之前移除了其中一个观察者,所以我们可以看到控制台只打印了一次消息
小案例-画彩虹
在了解完具体的观察者模式之后,我们来实现一个小案例。最终的效果如下图
实现出来的效果是,按住下方的小黑圈左右拖动,上方的7个彩虹颜色的div的宽度也会相应地放大和缩小。
在本案例中,小黑圈就是目标对象,7个彩虹颜色地div就是依附在该小黑圈中的观察者。当小黑圈的坐标发生变化时,需要通知给这些观察者,告诉他们需要改变自身的宽度。
下面来看具体的实现
html结构
样式部分省略
<div class="rainbow" style="background-color: rgb(255,0,0);"></div> <div class="rainbow" style="background-color: rgb(255,165,0);"></div> <div class="rainbow" style="background-color: rgb(255,255,0);"></div> <div class="rainbow" style="background-color: rgb(0,255,0);"></div> <div class="rainbow" style="background-color: rgb(0,127,255);"></div> <div class="rainbow" style="background-color: rgb(0,0,255);"></div> <div class="rainbow" style="background-color: rgb(139,0,255);"></div> <div id="progress-bar"></div> 复制代码
定义observer
class Observer { //item是需要充当观察者的7个div元素 constructor (item) { this.item = item } //观察者收到通知时,更新自身的width update (data) { this.item.style.width = data + 'px' } } 复制代码
定义观察者列表
//观察者列表,跟之前实现的差不多 class ObserverList { constructor () { this.list = [] } add (observers) { this.list.push(observers) } count () { return this.list.length } getIndexAt (index) { return this.list[index] } } 复制代码
目标对象
//目标对象的实现也基本上一样 class Subject { constructor (subject) { this.observers = new ObserverList() } addObserver (observers) { this.observers.add(observers) } notify (data) { const len = this.observers.count() for (let i = 0; i < len; i++) { this.observers.getIndexAt(i).update(data) } } } 复制代码
之后我们要做的就是,获取7个div元素充当观察者,并添加到我们实例化的目标对象上。如下
const rainbows = document.querySelectorAll('.rainbow') const bar = document.getElementById('progress-bar') const barSubject = new Subject() rainbows.forEach(item => { const observer = new Observer(item) barSubject.addObserver(observer) }) 复制代码
在上面的代码中,我们获取了7个div对象,并遍历用每一个div实例化一个Observer,并添加到目标对象上。
之后要做的就是实现目标对象的拖放,并在拖放过程中通知观察者即可,实现拖放的部分省略,这里给出通知给观察者的部分,如下
document.onmousemove = function (event) { event = event || window.event; const sl = document.body.scrollLeft || document.documentElement.scrollLeft; const left = event.clientX - ol; box.style.left = left + sl + "px"; //通知观察者 barSubject.notify(left + sl) } 复制代码
到此,案例就完成了。希望对大家有所帮助。有不对的地方欢迎大佬们指正。
注意:观察者模式和大家熟知的发布/订阅者模式是有区别的,我会在下一篇文章中介绍
作者:D_L11
链接:https://juejin.cn/post/7022934151665811463