摘要:觀察者模式維護(hù)單一事件對(duì)應(yīng)多個(gè)依賴該事件的對(duì)象關(guān)系發(fā)布訂閱維護(hù)多個(gè)事件主題及依賴各事件主題的對(duì)象之間的關(guān)系觀察者模式是目標(biāo)對(duì)象直接觸發(fā)通知全部通知,觀察對(duì)象被迫接收通知。
觀察者模式(Observer)
觀察者模式:定義了對(duì)象間一種一對(duì)多的依賴關(guān)系,當(dāng)目標(biāo)對(duì)象 Subject 的狀態(tài)發(fā)生改變時(shí),所有依賴它的對(duì)象 Observer 都會(huì)得到通知。
簡(jiǎn)單點(diǎn):女神有男朋友了,朋友圈曬個(gè)圖,甜蜜宣言 “老娘成功脫單,希望你們歡喜”。各位潛藏備胎紛紛失戀,只能安慰自己你不是唯一一個(gè)。
模式特征一個(gè)目標(biāo)者對(duì)象 Subject,擁有方法:添加 / 刪除 / 通知 Observer;
多個(gè)觀察者對(duì)象 Observer,擁有方法:接收 Subject 狀態(tài)變更通知并處理;
目標(biāo)對(duì)象 Subject 狀態(tài)變更時(shí),通知所有 Observer。
Subject 添加一系列 Observer, Subject 負(fù)責(zé)維護(hù)與這些 Observer 之間的聯(lián)系,“你對(duì)我有興趣,我更新就會(huì)通知你”。
代碼實(shí)現(xiàn)// 目標(biāo)者類 class Subject { constructor() { this.observers = []; // 觀察者列表 } // 添加 add(observer) { this.observers.push(observer); } // 刪除 remove(observer) { let idx = this.observers.findIndex(item => item === observer); idx > -1 && this.observers.splice(idx, 1); } // 通知 notify() { for (let observer of this.observers) { observer.update(); } } } // 觀察者類 class Observer { constructor(name) { this.name = name; } // 目標(biāo)對(duì)象更新時(shí)觸發(fā)的回調(diào) update() { console.log(`目標(biāo)者通知我更新了,我是:${this.name}`); } } // 實(shí)例化目標(biāo)者 let subject = new Subject(); // 實(shí)例化兩個(gè)觀察者 let obs1 = new Observer("前端開發(fā)者"); let obs2 = new Observer("后端開發(fā)者"); // 向目標(biāo)者添加觀察者 subject.add(obs1); subject.add(obs2); // 目標(biāo)者通知更新 subject.notify(); // 輸出: // 目標(biāo)者通知我更新了,我是前端開發(fā)者 // 目標(biāo)者通知我更新了,我是后端開發(fā)者優(yōu)勢(shì)
目標(biāo)者與觀察者,功能耦合度降低,專注自身功能邏輯;
觀察者被動(dòng)接收更新,時(shí)間上解耦,實(shí)時(shí)接收目標(biāo)者更新狀態(tài)。
不完美觀察者模式雖然實(shí)現(xiàn)了對(duì)象間依賴關(guān)系的低耦合,但卻不能對(duì)事件通知進(jìn)行細(xì)分管控,如 “篩選通知”,“指定主題事件通知” 。
比如上面的例子,僅通知 “前端開發(fā)者” ?觀察者對(duì)象如何只接收自己需要的更新通知?上例中,兩個(gè)觀察者接收目標(biāo)者狀態(tài)變更通知后,都執(zhí)行了 update(),并無區(qū)分。
“00后都在追求個(gè)性的時(shí)代,我能不能有點(diǎn)不一樣?”,這就引出我們的下一個(gè)模式。進(jìn)階版的觀察者模式。“發(fā)布訂閱模式”,部分文章對(duì)兩者是否一樣都存在爭(zhēng)議。
僅代表個(gè)人觀點(diǎn):兩種模式很類似,但是還是略有不同,就是多了個(gè)第三者,因 JavaScript 非正規(guī)面向?qū)ο笳Z(yǔ)言,且函數(shù)回調(diào)編程的特點(diǎn),使得 “發(fā)布訂閱模式” 在 JavaScript 中代碼實(shí)現(xiàn)可等同為 “觀察模式”。
發(fā)布訂閱模式(Publisher && Subscriber)發(fā)布訂閱模式:基于一個(gè)事件(主題)通道,希望接收通知的對(duì)象 Subscriber 通過自定義事件訂閱主題,被激活事件的對(duì)象 Publisher 通過發(fā)布主題事件的方式通知各個(gè)訂閱該主題的 Subscriber 對(duì)象。
發(fā)布訂閱模式與觀察者模式的不同,“第三者” (事件中心)出現(xiàn)。目標(biāo)對(duì)象并不直接通知觀察者,而是通過事件中心來派發(fā)通知。
代碼實(shí)現(xiàn)// 事件中心 let pubSub = { list: {}, subscribe: function (key, fn) { // 訂閱 if (!this.list[key]) { this.list[key] = []; } this.list[key].push(fn); }, publish: function(key, ...arg) { // 發(fā)布 for(let fn of this.list[key]) { fn.call(this, ...arg); } }, unSubscribe: function (key, fn) { // 取消訂閱 let fnList = this.list[key]; if (!fnList) return false; if (!fn) { // 不傳入指定取消的訂閱方法,則清空所有key下的訂閱 fnList && (fnList.length = 0); } else { fnList.forEach((item, index) => { if (item === fn) { fnList.splice(index, 1); } }) } } } // 訂閱 pubSub.subscribe("onwork", time => { console.log(`上班了:${time}`); }) pubSub.subscribe("offwork", time => { console.log(`下班了:${time}`); }) pubSub.subscribe("launch", time => { console.log(`吃飯了:${time}`); }) // 發(fā)布 pubSub.publish("offwork", "18:00:00"); pubSub.publish("launch", "12:00:00"); // 取消訂閱 pubSub.unSubscribe("onwork");
發(fā)布訂閱模式中,訂閱者各自實(shí)現(xiàn)不同的邏輯,且只接收自己對(duì)應(yīng)的事件通知。實(shí)現(xiàn)你想要的 “不一樣”。
DOM 事件監(jiān)聽也是 “發(fā)布訂閱模式” 的應(yīng)用:
let loginBtn = document.getElementById("#loginBtn"); // 監(jiān)聽回調(diào)函數(shù)(指定事件) function notifyClick() { console.log("我被點(diǎn)擊了"); } // 添加事件監(jiān)聽 loginBtn.addEventListener("click", notifyClick); // 觸發(fā)點(diǎn)擊, 事件中心派發(fā)指定事件 loginBtn.click(); // 取消事件監(jiān)聽 loginBtn.removeEventListener("click", notifyClick);
發(fā)布訂閱的通知順序:
先訂閱后發(fā)布時(shí)才通知(常規(guī))
訂閱后可獲取過往以后的發(fā)布通知 (QQ離線消息,上線后獲取之前的信息)
流行庫(kù)的應(yīng)用jQuery 的 on 和 trigger,$.callback();
Vue 的雙向數(shù)據(jù)綁定;
Vue 的父子組件通信 $on/$emit
jQuery 的 $.Callback() 更像是觀察者模式的應(yīng)用,不能更細(xì)粒度管控。
function notifyHim(value) { console.log("He say " + value); } function notifyHer(value) { console.log("She say " + value); } $cb = $.Callbacks(); // 聲明一個(gè)回調(diào)容器:訂閱列表 $cb.add(notifyHim); // 向回調(diào)列表添加回調(diào):訂閱 $cb.add(notifyHer); // 向回調(diào)列表添加回調(diào):訂閱 $cb.fire("help"); // 調(diào)用所有回調(diào): 發(fā)布
利用 Object.defineProperty() 對(duì)數(shù)據(jù)進(jìn)行劫持,設(shè)置一個(gè)監(jiān)聽器 Observer,用來監(jiān)聽數(shù)據(jù)對(duì)象的屬性,如果屬性上發(fā)生變化了,交由 Dep 通知訂閱者 Watcher 去更新數(shù)據(jù),最后指令解析器 Compile 解析對(duì)應(yīng)的指令,進(jìn)而會(huì)執(zhí)行對(duì)應(yīng)的更新函數(shù),從而更新視圖,實(shí)現(xiàn)了雙向綁定。
Observer (數(shù)據(jù)劫持)
Dep (發(fā)布訂閱)
Watcher (數(shù)據(jù)監(jiān)聽)
Compile (模版編譯)
關(guān)于 Vue 雙向數(shù)據(jù)綁定原理,可自行參考其它文章,或推薦本篇 《 vue雙向數(shù)據(jù)綁定原理》。
Vue源碼傳送門
Vue 中,父組件通過 props 向子組件傳遞數(shù)據(jù)(自上而下的單向數(shù)據(jù)流)。父子組件之間的通信,通過自定義事件即 $on , $emit 來實(shí)現(xiàn)(子組件 $emit,父組件 $on)。
原理其實(shí)就是 $emit 發(fā)布更新通知,而 $on 訂閱接收通知。Vue 中還實(shí)現(xiàn)了 $once(一次監(jiān)聽),$off(取消訂閱)。
// 訂閱 vm.$on("test", function (msg) { console.log(msg) }) // 發(fā)布 vm.$emit("test", "hi")
Vue源碼傳送門
Vue文檔傳送門
優(yōu)勢(shì)對(duì)象間功能解耦,弱化對(duì)象間的引用關(guān)系;
更細(xì)粒度地管控,分發(fā)指定訂閱主題通知
不完美對(duì)間間解耦后,代碼閱讀不夠直觀,不易維護(hù);
額外對(duì)象創(chuàng)建,消耗時(shí)間和內(nèi)存(很多設(shè)計(jì)模式的通病)
觀察者模式 VS 發(fā)布訂閱模式 類似點(diǎn)都是定義一個(gè)一對(duì)多的依賴關(guān)系,有關(guān)狀態(tài)發(fā)生變更時(shí)執(zhí)行相應(yīng)的通知。
區(qū)別點(diǎn)發(fā)布訂閱模式更靈活,是進(jìn)階版的觀察者模式,指定對(duì)應(yīng)分發(fā)。
觀察者模式維護(hù)單一事件對(duì)應(yīng)多個(gè)依賴該事件的對(duì)象關(guān)系;
發(fā)布訂閱維護(hù)多個(gè)事件(主題)及依賴各事件(主題)的對(duì)象之間的關(guān)系;
觀察者模式是目標(biāo)對(duì)象直接觸發(fā)通知(全部通知),觀察對(duì)象被迫接收通知。發(fā)布訂閱模式多了個(gè)中間層(事件中心),由其去管理通知廣播(只通知訂閱對(duì)應(yīng)事件的對(duì)象);
觀察者模式對(duì)象間依賴關(guān)系較強(qiáng),發(fā)布訂閱模式中對(duì)象之間實(shí)現(xiàn)真正的解耦。
對(duì)象屬性數(shù)據(jù)攔截方式:Object.defineProperty() 屬性描述符;
ES6 Class set ;
ES6 Proxy 代理;
參考文章:
談?wù)動(dòng)^察者模式和發(fā)布訂閱模式
原生JavaScript實(shí)現(xiàn)觀察者模式
觀察者模式 vs 發(fā)布訂閱模式
vue雙向數(shù)據(jù)綁定原理
本文首發(fā)Github,期待Star!
https://github.com/ZengLingYong/blog
作者:以樂之名
本文原創(chuàng),有不當(dāng)?shù)牡胤綒g迎指出。轉(zhuǎn)載請(qǐng)指明出處。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105523.html
摘要:設(shè)計(jì)模式與開發(fā)實(shí)踐讀書筆記。發(fā)布訂閱模式又叫觀察者模式,它定義了對(duì)象之間的一種一對(duì)多的依賴關(guān)系。附設(shè)計(jì)模式之發(fā)布訂閱模式觀察者模式數(shù)據(jù)結(jié)構(gòu)和算法系列棧隊(duì)列優(yōu)先隊(duì)列循環(huán)隊(duì)列設(shè)計(jì)模式系列設(shè)計(jì)模式之策略模式 《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)踐》讀書筆記。 發(fā)布-訂閱模式又叫觀察者模式,它定義了對(duì)象之間的一種一對(duì)多的依賴關(guān)系。當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴它的對(duì)象都將得到通知。 例...
摘要:發(fā)布訂閱模式訂閱者把自己想訂閱的事件注冊(cè)到調(diào)度中心,當(dāng)發(fā)布者發(fā)布該事件到調(diào)度中心,也就是該事件觸發(fā)時(shí),由調(diào)度中心統(tǒng)一調(diào)度訂閱者注冊(cè)到調(diào)度中心的處理代碼。 發(fā)布-訂閱模式,看似陌生,其實(shí)不然。工作中經(jīng)常會(huì)用到,例如 Node.js EventEmitter 中的 on 和 emit 方法;Vue 中的 $on 和 $emit 方法。他們都使用了發(fā)布-訂閱模式,讓開發(fā)變得更加高效方便。 一...
摘要:設(shè)計(jì)模式與開發(fā)實(shí)踐讀書筆記。看此文章前,建議先看設(shè)計(jì)模式之發(fā)布訂閱模式觀察者模式在中,已經(jīng)介紹了什么是發(fā)布訂閱模式,同時(shí),也實(shí)現(xiàn)了發(fā)布訂閱模式。 《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)踐》讀書筆記。 看此文章前,建議先看JavaScript設(shè)計(jì)模式之發(fā)布-訂閱模式(觀察者模式)-Part1 在Part1中,已經(jīng)介紹了什么是發(fā)布-訂閱模式,同時(shí),也實(shí)現(xiàn)了發(fā)布-訂閱模式。但是,就Part1...
閱讀 1581·2021-11-25 09:43
閱讀 2374·2019-08-30 15:55
閱讀 1488·2019-08-30 13:08
閱讀 2722·2019-08-29 10:59
閱讀 846·2019-08-29 10:54
閱讀 1618·2019-08-26 18:26
閱讀 2587·2019-08-26 13:44
閱讀 2683·2019-08-23 18:36