一、观察者模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。 —— Graphic Design Patterns
单看看上面的定义比较不好理解,观察者就是由一个发布者和一个订阅者或多个订阅者组成,比如在一个群聊群主可以@任何成员并发布消息,群主就是一个发布者,其他的成员接收到群主的消息并执行任务就是订阅者。
需要注意的是观察者模式和发布-订阅模式有一定的区别,具体的区别后面会讲!
1.实现一个观察者模式
首先创建一个发布者,这个发布者具有以下功能 可以添加订阅者,移出订阅者,通知订阅者。可以理解为群主是发布者可以邀请新成员、踢出成员、并且@通知所有成员
class Publisher {
constructor() {
this.observers = []
console.log('Publisher created')
}
// 增加订阅者
add(observer) {
console.log('Publisher.add invoked')
this.observers.push(observer)
}
// 移除订阅者
remove(observer) {
console.log('Publisher.remove invoked')
this.observers.forEach((item, i) => {
if (item === observer) {
this.observers.splice(i, 1)
}
})
}
// 通知所有订阅者
notify() {
console.log('Publisher.notify invoked')
this.observers.forEach((observer) => {
observer.update(this)
})
}
}
订阅者就是去执行发布者的任务
class Observer {
constructor() {
console.log('Observer created');
}
update() {
console.log('执行任务');
}
}
测试 创建订阅者和创建发布者,并添加订阅者,和移出订阅者
const liLei = new Observer()
const limeishan = new Publisher()
limeishan.add(liLei)
limeishan.remove(liLei)
然后我们可以新增一个setState方法用来更新文档,当文档更新后就直接通知所有的订阅者接收新的文档并开始工作
class Publisher {
constructor() {
this.observers = []
console.log('Publisher created')
}
// 增加订阅者
add(observer) {
console.log('Publisher.add invoked')
this.observers.push(observer)
}
// 移除订阅者
remove(observer) {
console.log('Publisher.remove invoked')
this.observers.forEach((item, i) => {
if (item === observer) {
this.observers.splice(i, 1)
}
})
}
// 通知所有订阅者
notify() {
console.log('Publisher.notify invoked')
this.observers.forEach((observer) => {
observer.update(this)
})
}
}
//创建订阅者
//订阅者就是去执行发布者的方法任务
class Observer {
constructor() {
console.log('Observer created');
}
update() {
console.log('执行任务');
}
}
// 定义一个具体的需求文档(prd)发布类
class PrdPublisher extends Publisher {
constructor() {
super()
// 初始化需求文档
this.prdState = null
// 韩梅梅还没有拉群,开发群目前为空
this.observers = []
console.log('PrdPublisher created')
}
// 该方法用于获取当前的prdState
getState() {
console.log('PrdPublisher.getState invoked')
return this.prdState
}
// 该方法用于改变prdState的值
setState(state) {
console.log('PrdPublisher.setState invoked')
// prd的值发生改变
this.prdState = state
// 需求文档变更,立刻通知所有开发者
this.notify()
}
}
class DeveloperObserver extends Observer {
constructor() {
super()
// 需求文档一开始还不存在,prd初始为空对象
this.prdState = {}
console.log('DeveloperObserver created')
}
// 重写一个具体的update方法
update(publisher) {
console.log('DeveloperObserver.update invoked')
// 更新需求文档
this.prdState = publisher.getState()
// 调用工作函数
this.work()
}
// work方法,一个专门搬砖的方法
work() {
// 获取需求文档
const prd = this.prdState
// 开始基于需求文档提供的信息搬砖。。。
console.log('996 begins...')
}
}
2.完成vue2响应式原理
具体可以参考这篇文章juejin.cn/book/684473…
Watcher实例存放到对应的Dep对象中去。get方法可以让当前的Watcher对象(Dep.target)存放到它的 subs 中(addSub)方法,在数据变化时,set会调用Dep对象的notify方法通知它内部所有的Watcher对象进行视图更新。
function observer(target) {
if (target && typeof target === 'object') {
Object.keys(target).forEach((key) => {
defineReactive(target, key, target[key])
})
}
}
function defineReactive(target, key, val) {
//属性值可能是object 需要进行递归
const dep = new Dep()
observer(val)
Object.defineProperty(target, key, {
enumerable: true,
configurable: false,
get: function () {
return val
},
set: function (value) {
dep.notify()
}
})
}
//实现订阅者
class Dep {
constructor() {
this.subs = []
}
addSub(subs) {
this.subs.push(subs)
}
notify() {
this.subs.forEach((item) => {
item.update()
})
}
}
3.实现发布-订阅模式
实现一个全局事件总线
class EventEmitter {
constructor() {
// handlers是一个map,用于存储事件与回调之间的对应关系
this.handlers = {}
}
//订阅事件
on(eventName, callback) {
if (!this.handlers[eventName]) {
this.handlers[eventName] = []
}
this.handlers[eventName].push(callback)
console.log(this.handlers);
}
//触发事件
emit(eventName) {
if (this.handlers[eventName]) {
const handlers = this.handlers[eventName]
handlers.forEach(element => {
element()
});
}
}
//取消订阅
off(eventName, cb) {
const callbacks = this.handlers[eventName]
let index = callbacks && callbacks.indexOf(cb)
if (index != -1) {
callbacks.splice(index, 1)
}
}
//仅仅订阅一次
once(eventName, cb) {
if (this.handlers[eventName] && this.handlers[eventName].indexOf(cb)) {
return
}
this.on(eventName, cb)
}
}
const emitter = new EventEmitter()
emitter.on('change', () => {
console.log(123)
})
emitter.emit('change')
观察者模式和发布-订阅模式区别
由发布者直接触及到订阅者的操作,叫观察者模式。
由发布者不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作,叫做发布-订阅模式。