在软件系统中常常碰到这类需求:当一个对象的状态发生改变,某些与它相关的对象也要随之作出相应的变化。这是创建一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其余对象,其余对象将相应作出反应。javascript
咱们将发生改变的对象称为观察目标,将被通知的对象称为观察者,一个观察目标能够对应多个观察者,并且这些观察者之间没有相互联系,以后能够根据须要增长和删除观察者,使得系统更易于扩展,这就是观察者模式的产生背景。html
观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆获得通知并被自动更新。观察者模式是一种对象行为型模式。前端
在全部浏览器事件(鼠标悬停,按键等事件)都是观察者模式的例子。vue
另外还有:java
如咱们订阅微信公众号“前端自习课”(观察目标),当“前端自习课”群发图文消息后,全部公众号粉丝(观察者)都会接收到这篇文章(事件),这篇文章的内容是发布者自定义的(自定义事件),粉丝阅读后做出特定操做(如:点赞,收藏,关注等)。react
在观察者模式中,一般包含如下角色:面试
图片来源:《TypeScript 设计模式之观察者模式》 typescript
在如下状况下可使用观察者模式:设计模式
// ObserverPattern.ts // 观察目标接口 interface Subject { addObserver: (observer: Observer) => void; deleteObserver: (observer: Observer) => void; notifyObservers: () => void; } // 观察者接口 interface Observer { notify: () => void; }
// ObserverPattern.ts // 具体观察目标类 class ConcreteSubject implements Subject{ private observers: Observer[] = []; // 添加观察者 public addObserver(observer: Observer): void { console.log(observer, " is pushed~~"); this.observers.push(observer); } // 移除观察者 public deleteObserver(observer: Observer): void { console.log(observer, " have deleted~~"); const idx: number = this.observers.indexOf(observer); ~idx && this.observers.splice(idx, 1); } // 通知观察者 public notifyObservers(): void { console.log("notify all the observers ", this.observers); this.observers.forEach(observer => { // 调用 notify 方法时能够携带指定参数 observer.notify(); }); } }
// ObserverPattern.ts // 具体观 class ConcreteObserver implements Observer{ constructor(private name: string) {} notify(): void { // 能够处理其余逻辑 console.log(`${this.name} has been notified.`); } }
// ObserverPattern.ts function useObserver(): void { const subject: Subject = new ConcreteSubject(); const Leo = new ConcreteObserver("Leo"); const Robin = new ConcreteObserver("Robin"); const Pual = new ConcreteObserver("Pual"); const Lisa = new ConcreteObserver("Lisa"); subject.addObserver(Leo); subject.addObserver(Robin); subject.addObserver(Pual); subject.addObserver(Lisa); subject.notifyObservers(); subject.deleteObserver(Pual); subject.deleteObserver(Lisa); subject.notifyObservers(); } useObserver();
完整演示代码以下:浏览器
// ObserverPattern.ts interface Subject { addObserver: (observer: Observer) => void; deleteObserver: (observer: Observer) => void; notifyObservers: () => void; } interface Observer { notify: () => void; } class ConcreteSubject implements Subject{ private observers: Observer[] = []; public addObserver(observer: Observer): void { console.log(observer, " is pushed~~"); this.observers.push(observer); } public deleteObserver(observer: Observer): void { console.log(observer, " have deleted~~"); const idx: number = this.observers.indexOf(observer); ~idx && this.observers.splice(idx, 1); } public notifyObservers(): void { console.log("notify all the observers ", this.observers); this.observers.forEach(observer => { // 调用 notify 方法时能够携带指定参数 observer.notify(); }); } } class ConcreteObserver implements Observer{ constructor(private name: string) {} notify(): void { // 能够处理其余逻辑 console.log(`${this.name} has been notified.`); } } function useObserver(): void { const subject: Subject = new ConcreteSubject(); const Leo = new ConcreteObserver("Leo"); const Robin = new ConcreteObserver("Robin"); const Pual = new ConcreteObserver("Pual"); const Lisa = new ConcreteObserver("Lisa"); subject.addObserver(Leo); subject.addObserver(Robin); subject.addObserver(Pual); subject.addObserver(Lisa); subject.notifyObservers(); subject.deleteObserver(Pual); subject.deleteObserver(Lisa); subject.notifyObservers(); } useObserver();
在 Vue.js 中,当咱们修改数据状时,视图随之更新,这就是 Vue.js 的双向数据绑定(也称响应式原理),这是 Vue.js 中最独特的特性之一。
若是你对 Vue.js 的双向数据绑定还不清楚,建议先阅读官方文档《深刻响应式原理》章节。
在官网中提供这么一张流程图,介绍了 Vue.js 响应式系统的整个流程:
图片来自:Vue.js 官网《深刻响应式原理》
在 Vue.js 中,每一个组件实例都对应一个 watcher 实例,它会在组件渲染的过程当中把“接触”(“Touch” 过程)过的数据 property 记录为依赖(Collect as Dependency 过程)。以后当依赖项的 setter 触发时,会通知 watcher(Notify 过程),从而使它关联的组件从新渲染(Trigger re-render 过程)——这是一个典型的观察者模式。
这道面试题考察面试者对 Vue.js 底层原理的理解、对观察者模式的实现能力以及一系列重要的JS知识点,具备较强的综合性和表明性。
在 Vue.js 数据双向绑定的实现逻辑中,包含三个关键角色:
这三者的配合过程如图所示:
图片来自:掘金小册《JavaScript 设计模式核⼼原理与应⽤实践》
首先咱们须要实现一个方法,这个方法会对须要监听的数据对象进行遍历、给它的属性加上定制的 getter
和 setter
函数。这样但凡这个对象的某个属性发生了改变,就会触发 setter
函数,进而通知到订阅者。这个 setter
函数,就是咱们的监听器:
// observe方法遍历并包装对象属性 function observe(target) { // 若target是一个对象,则遍历它 if(target && typeof target === 'object') { Object.keys(target).forEach((key)=> { // defineReactive方法会给目标属性装上“监听器” defineReactive(target, key, target[key]) }) } } // 定义defineReactive方法 function defineReactive(target, key, val) { // 属性值也多是object类型,这种状况下须要调用observe进行递归遍历 observe(val) // 为当前属性安装监听器 Object.defineProperty(target, key, { // 可枚举 enumerable: true, // 不可配置 configurable: false, get: function () { return val; }, // 监听器函数 set: function (value) { console.log(`${target}属性的${key}属性从${val}值变成了了${value}`) val = value } }); }
下面实现订阅者 Dep
:
// 定义订阅者类Dep class Dep { constructor() { // 初始化订阅队列 this.subs = [] } // 增长订阅者 addSub(sub) { this.subs.push(sub) } // 通知订阅者(是否是全部的代码都似曾相识?) notify() { this.subs.forEach((sub)=>{ sub.update() }) } }
如今咱们能够改写 defineReactive
中的 setter
方法,在监听器里去通知订阅者了:
function defineReactive(target, key, val) { const dep = new Dep() // 监听当前属性 observe(val) Object.defineProperty(target, key, { set: (value) => { // 通知全部订阅者 dep.notify() } }) }
观察者模式又称发布-订阅模式、模型-视图模式、源-监听器模式或从属者模式。是一种对象行为型模式。其定义了一种对象间的一对多依赖关系,当观察目标发生状态变化,会通知全部观察者对象,使它们自动更新。
在实际业务中,若是一个对象的行为依赖于另外一个对象的状态。或者说当目标对象的状态发生改变时,会直接影响到观察者的行为,尽可能考虑到使用观察者模式来实现。
观察者模式和发布-订阅模式二者很像,但其实区别比较大。例如:
下一篇文章见。
1.《3. 观察者模式》
2.《TypeScript 设计模式之观察者模式》
3.《JavaScript 设计模式核⼼原理与应⽤实践》