在軟件系統中常常碰到這類需求:當一個對象的狀態發生改變,某些與它相關的對象也要隨之作出相應的變化。這是創建一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其餘對象,其餘對象將相應作出反應。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 設計模式核⼼原理與應⽤實踐》