1、什么是观察者模式?
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一目标对象,
当这个目标对象的状态发生变化时,会通知所有观察者对象,使他们能够自动更新。
2、观察者模式和发布-订阅模式的区别
- 观察者模式在js设计模式中使用频率最高,在面试中被问到的概率最高,观察者模式‘别名’发布-订阅模式,这两者之间还是不能严格划等号的;
- 发布-订阅模式:发布者不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作;
- 两者的区别主要在于是否存在第三方、发布者能否直接感知订阅者;
- 观察者解决的是模块间的耦合问题,可以实现两个独立模块间的数据通信,但是它没有完全解决耦合问题,被观察者必须要维护一套观察者集合;
- 发布-订阅模式,发布者完全不用感知订阅者,事件的注册和触发都发生在独立于双方的第三方平台(事件总线)上,实现了完全的解耦;
- 两者的使用场景不同,各有各的适用场景:
- 如果两个模块之间本身存在关联,且这种关联是稳定的、必要的,那么我们使用观察者模式就足够了。而在模块与模块之间独立性较强、且没有必要单纯为了数据通信而强行为两者制造依赖的情况下,我们往往会倾向于使用发布-订阅模式。
3、vue数据双向绑定(响应式系统)的实现
3.1 vue中的观察者模式应用
- 在vue中,每个组件的实例都有相应的watcher实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使关联组件得以更新——这就是一个典型的观察者模式。
- vue官网中响应式系统的流程图

3.2 实现一个简单的vue响应式代码
在vue数据双向绑定的实现逻辑里,有这样三个关键角色:
- observer(监听器):在vue数据双向绑定的角色结构里,所谓的observer不仅是一个数据监听器,它还需要对监听到的数据进行转发,它同时还是一个发布者;
- wathcer(订阅者):observer把数据转发给了真正的订阅者——watcher对象。watcher接收到新的数据后,会去更新视图;
- compile(编译器):负责对每个节点元素指令进行扫描和解析,指令的数据初始化、订阅者的创建;
核心代码:
function observe(target){
if(target && typeof target === 'object'){
Object.keys(target).forEach((key)=>{
defineReactive(target, key, target[key])
})
}
}
function defineReactive(target, key, val){
observe(val)
Object.defineProperty(target,key,{
enumerable:true,
configurable;false,
get:function(){
return val
},
set:function(newVal){
val = newVal
dep.notify()
}
})
}
class Dep{
constructor(){
this.subs = []
}
addSub(sub){
this.subs.push(sub)
}
notify(){
this.subs.forEach((sub)=>{
sub.update()
})
}
}
4、实现一个Event Bus/Event Emitter
Event Bus/Event Emitter作为全局事件总线,它起到一个沟通桥梁的作用,是一个事件中心。
实现一个Event Bus
class EventEmitter {
constructor(){
this.handlers = {}
}
on(evnetName, cb){
if(!this.handlers[eventName]){
this.handlers[evnetName] = []
}
this.handlers[eventName].push(cb)
}
emit(eventName,...args){
if(this.handlers[eventName]){
主要目的是为了避免通过once安装的监听器在移除的过程中出现顺序问题
const handlers = this.handlers[eventName].slice()
handlers.forEach((callback)=>{
callback(...args)
})
}
}
off(eventName, cb){
const callbacks = this.handlers[eventName]
const index = callbacks.indexOf(cb)
if(index !== -1){
callbacks.splice(index,1)
}
}
once(eventName,cb){
const wrapper = (...args)=>{
cb(...args)
this.off(eventName, wrapper)
}
this.on(eventNme,wrapper)
}
}