一、什么是观察者模式
当对象之间存在一对多的依赖关系时,其中一个对象的状态发生改变,所有依赖它的对象都会收到通知,这就是观察者模式。
二、实际场景
1. DOM 事件
开发过程中,最常见的观察者模式场景就是 DOM 事件函数,先看看代码:
document.body.addEventListener('click', () => {
alert(2)
}, false)
当 body
节点被点击时,触发 alert(2)
,从观察者模式来解释,就是我们订阅了 bdoy
节点的点击事件,当点击事件触发,我们就会收到通知。
2. 网站登录
网站登录功能,想必大多数做过平台需求的同学都实现过,当网站中的不同模块,如Header模块/Nav模块/正文模块,都依赖登录后获取的用户数据时,该怎么去实现呢?
2.1 普通做法
先看代码:
login.succ((data => {
header.setAvatar(data.avatar) // 设置头像
nav.setAvatar(data.avatar) // 设置导航区的头像
list.refresh() // 刷新列表
})
这样的代码是不是特别熟悉?把依赖的方法,放在回调函数中。上述就是在登录成功的回调函数中,添加了各模块的方法。这么做导致各个模块和登录模块高度耦合,当新增了一个地址栏模块时,不得不再次修改登录模块的回调函数,违反了开放-封闭原则。
2.2 观察者模式
用观察者模式,优化上述需求。
登录模块是一个订阅对象,Header模块/Nav模块/正文模块添加对登录模块的订阅,当登录模块发生改变时,通知各个订阅了登录模块的模块。代码如下:
// 登录模块js
// 登录成功后,发布“loginSucc”登录成功消息,并传递data数据
login.succ(data=> {
login.trigger('loginSucc', data)
})
// header模块js
// 订阅“loginSucc”登录成功消息
login.listen('loginSucc', () => {
header.setAvatar(data.avatar)
})
// nav模块js
// 订阅“loginSucc”登录成功消息
login.listen('loginSucc', () => {
nav.setAvatar(data.avatar)
})
上述代码用观察者模式重构了网站登录功能,后续不管新增多少业务模块,依赖登录功能,都只需要在模块内新增对登录成功的订阅,无需再改动登录模块。
3. 双向数据绑定
双向数据绑定也可以通过观察者模式实现。
双向指的是视图 view
和模型 model
,当视图发生改变时,模型也发生变化,同样,当模型发生改变,视图也跟着同步变化。
分为以下几个步骤实现:
3.1 新建发布-订阅对象
新建一个发布-订阅对象,用于发布消息,订阅消息。
- subscrib:订阅函数,当其他对象添加订阅消息时,将回调函数 push 进 callbacks 对象数组中;
-
publish:发布函数,当发布消息时,触发 callbacks 中该消息对应的 callback.
const Pubsub = { subscrib: function (ev, callback) { this._callbacks || (this._callbacks = {}); (this._callbacks[ev] || (this._callbacks[ev] = [])).push(callback); }, publish: function () { const args = [...arguments] const ev = args.shift() if (!this._callbacks) return if (!this._callbacks[ev]) return this._callbacks[ev].forEach(callback => { callback(...args) }) } }
3.2 ui 更新
3.2.1 发布 ui 更新消息
注册
document
的keyup
/change
事件,当激活事件的dom
元素拥有data-bind
属性时,说明 ui 正在更新,发布 ui 更新消息通知订阅者。function eventHander (e) { const { target } = e const { value: propValue } = target const propNameWhole = target.getAttribute('data-bind') if (propNameWhole) { // 发布ui更新消息 Pubsub.publish('ui-update-event', { propNameWhole, propValue }) } } console.log(document.addEventListener) document.addEventListener('change', eventHander, false) document.addEventListener('keyup', eventHander, false)
3.2.2 订阅 model 更新消息
所有包含 data-bind
属性的 dom
元素,订阅 model
更新消息,当 model
更新时,ui 将会收到通知。
// 订阅model更新消息,更新后所有符合条件的dom节点都会收到通知,进行更新
Pubsub.subscrib('model-update-event', function ({propNameWhole, propValue}) {
const elements = document.querySelectorAll(`[data-bind="${propNameWhole}"]`)
elements.forEach(element => {
const elementTagName = element.tagName.toLowerCase()
const formTypeTagNames = ['input', 'select', 'textarea']
if (formTypeTagNames.includes(elementTagName)) {
element.value = propValue
} else {
element.innerHTML = propValue
}
})
})
3.3 model 更新
3.3.1 订阅 ui 更新消息
订阅 ui 更新消息,当 ui 更新时,触发 modal 更新。
class Bind {
constructor () {
this.modelName = ''
}
initModel ({ modelName }) {
this.modelName = modelName
// 订阅ui更新消息
Pubsub.subscrib('ui-update-event', ({propNameWhole, propValue}) => {
const [ , _propName] = propNameWhole.split('.')
this.updateModalData(_propName, propValue)
})
}
// xxx省略xxx
updateModalData (propName, propValue) {
const propNameWhole = `${this.modelName}.${propName}`
// 发布model更新消息
Pubsub.publish('model-update-event', { propNameWhole, propValue });
}
}
3.3.2 发布model更新消息
model 更新时,发布 model 更新消息,此时,订阅了 model 更新消息的 ui,将得到通知。
class Bind {
constructor () {
this.modelName = ''
}
// xxx省略xxx
loadModalData (modelData) {
for (let propName in modelData) {
this.updateModalData(propName, modelData[propName])
}
}
updateModalData (propName, propValue) {
const propNameWhole = `${this.modelName}.${propName}`
// 发布model更新消息
Pubsub.publish('model-update-event', { propNameWhole, propValue });
}
}
完整源码见:github
三、总结
从上文的实际场景例子可见,观察者模式建立了一套触发机制,帮助我们完成更松耦合的代码编写。但是也不能过度使用,否则会导致程序难以追踪和理解。有以下场景适用于观察者模式:
一个抽象模型的一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
一个对象必须通知其他对象,而并不知道这些对象是谁。
需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
可通过github源码进行实操练习。希望本文能对你有所帮助,感谢阅读❤️~
作者:清汤饺子
链接:https://juejin.cn/post/6970724597029142564
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
转载请注明:有爱前端 » 设计模式-简单易懂的观察者模式(四)