javascript(js) 觀察者模式和釋出訂閱模式
文章目錄
參考文件
問題描述
最近想學習一下Vue 原始碼,在設定$data的值的時候,是如何通知模板變化的,其中就用到了 “訂閱-釋出”模式,發現對此思路不是很清晰,因此寫個學習筆記加強鞏固
觀察者模式
觀察者模式,目標和觀察者是基類,目標提供維護觀察者的一系列方法,觀察者提供更新介面。具體觀察者和具體目標繼承各自的基類,然後具體觀察者把自己註冊到具體目標裡,在具體目標發生變化時候,排程觀察者的更新方法。
個人理解就是目標發生變化通知觀察者,即觀察者模式是由具體目標排程的
比如有個“天氣中心”的具體目標A,專門監聽天氣變化,而有個顯示天氣的介面的觀察者B,B就把自己註冊到A裡,當A觸發天氣變化,就排程B的更新方法,並帶上自己的上下文。
觀察者模式 demo
class 描述觀察者模式
// 定義主題,即觀察者觀察的物件(目標)
class Subject {
constructor (name) {
this.name = name
this.observers = [] // 用來儲存觀察者
this.state = '心情好'
}
// 新增觀察者
attach (observer) {
this.observers.push(observer)
}
// 一旦被觀察的資訊發生變化,則通知觀察者
setState (newState) {
this.state = newState
this.observers.forEach(observer => {
observer.update(newState)
})
}
}
// 定義觀察者
class Observer {
constructor (name) {
this.name = name
}
// 定義觀察者針對資料發生變化做出的響應
update (newState) {
console.log(this.name + '處理:' + newState)
}
}
const subject = new Subject('我是新聞')
const observerOne = new Observer('我是讀者11')
const observerTwo = new Observer('我是讀者22')
subject.attach(observerOne)
subject.attach(observerTwo)
subject.setState('改變新聞了')
事件的觀察者模式
class Events {
constructor () {
this.callbacks = []
}
// 訂閱
on (callback) {
this.callbacks.push(callback)
}
// 釋出
emit (...data) {
this.callbacks.forEach(fn => {
fn(...data)
})
}
}
const eventBus = new Events()
eventBus.on(function (...data) {
console.log('監聽到資料變化1')
if (data.length === 1) {
console.log('我處理一個資料' + data)
}
})
eventBus.on(function (...data) {
console.log('監聽到資料變化2')
if (data.length === 2) {
console.log('我處理2個資料' + data)
}
})
eventBus.on(function (...data) {
console.log('監聽到資料變化3')
if (data.length > 2) {
console.log('我處理2個以上的資料' + data)
}
})
eventBus.emit('one')
eventBus.emit('two', 'param2')
eventBus.emit('three', 'param2', 'param3')
陣列塌陷
let _subscribe = (function () {
class Sub {
constructor () {
// 1. 建立一個事件池,用來儲存後期需要執行的方法
this.$pond = []
}
// 2. 向事件池中追加方法(重複處理)
add (func) {
let flag = this.$pond.some(item => {
return item === func
})
!flag ? this.$pond.push(func) : null
}
// 3. 從事件池中移除方法
remove (func) {
let $pond = this.$pond
for (let i = 0; i < $pond.length; i++) {
let item = $pond[i]
if (item === func) {
// 移除 (順序不變的情況下基本上只能用splice了)
// 不能這樣寫,會導致陣列塌陷問題,我們不能真移除,只能把當前項賦值為 Null
// $pond.splice(i, 1)
$pond[i] = null
break
}
}
}
// 4. 通知事件池中的方法,按照順序依次執行
fire (...args) {
console.log(arguments)
console.log(args)
const $pond = this.$pond
for (let i = 0; i < $pond.length; i++) {
let item = $pond[i]
if (typeof item !== 'function') {
// 此時再刪除
$pond.splice(i, 1)
i--
continue
}
item.call(this, ...args)
}
}
}
// 暴露給外面的使用
return function () {
return new Sub()
}
}())
// 測試
function test () {
let s1 = _subscribe()
let fn1 = function () {
console.log(1)
}
let fn2 = function () {
console.log(2)
// 模擬陣列塌陷的現象
s1.remove(fn1)
}
s1.add(fn1)
s1.add(fn2)
s1.add(fn1)
let fn3 = function () {
console.log(3)
}
let fn4 = function () {
console.log(4)
}
s1.add(fn3)
s1.add(fn4)
s1.fire('huang', 'biao')
}
test()
釋出/訂閱模式
釋出/訂閱模式,訂閱者把自己想訂閱的事件註冊到排程中心,當該事件觸發時候,釋出者釋出該事件到排程中心(順帶上下文),由排程中心統一排程訂閱者註冊到排程中心的處理程式碼。
比如有個介面是實時顯示天氣,它就訂閱天氣事件(註冊到排程中心,包括處理程式),當天氣變化時(定時獲取資料),就作為釋出者釋出天氣資訊到排程中心,排程中心就排程訂閱者的天氣處理程式。
個人理解:可以選擇主題,觸發關心該主題的訂閱者
釋出訂閱的demo
// 釋出、訂閱模式
let _subscribe = function () {
var pubsub = {
// 用來儲存 “主題” 和 訂閱者物件
topics: {},
// 記錄每個訂閱者物件的唯一標識
subUid: -1,
// 釋出指定訂閱
publish: function (topic, ...args) {
if (!this.topics[topic]) {
return false
}
var subscribers = this.topics[topic]
var len = subscribers ? subscribers.length : 0
while (len--) {
subscribers[len].func(topic, ...args)
}
return this
},
// 向訂閱中心新增訂閱
subscribe: function (topic, func) {
// 如果之前沒有新增 “主題”,則新增一個主題,值為陣列,用來儲存訂閱者回撥函式
if (!this.topics[topic]) {
this.topics[topic] = []
}
var token = (++this.subUid).toString()
// 主題下面有多個訂閱者,允許重複,token 為訂閱者的唯一標識
this.topics[topic].push({
token: token,
func: func
})
return token
},
// 向訂閱中移除訂閱
unSubscribe: function (token) {
// 遍歷所有的主題
for (var m in this.topics) {
// 主題存在值
if (this.topics[m]) {
// 遍歷主題的每個訂閱者,根據token找到唯一的訂閱者的值,然後去掉
for (var i = 0, j = this.topics[m].length; i < j; i++) {
if (this.topics[m][i].token === token) {
// 使用 splice 同樣會存在陣列塌陷的問題
this.topics[m].splice(i, 1)
// 將刪除的訂閱者設定為 null, 再執行訂閱者處理響應的時候刪除掉相關訂閱者
// this.topics[m] = null
return token
}
}
}
}
return this
}
}
return pubsub
}
function testAction () {
const pubsub = _subscribe()
console.log(pubsub.subscribe('test', (...args) => {
console.log('hello world1:' + args)
// pubsub.unSubscribe(0)
}))
console.log(pubsub.subscribe('test', (...args) => {
console.log('hello world2:' + args)
// pubsub.unSubscribe(1)
}))
console.log(pubsub.subscribe('test', (...args) => {
console.log('hello world3:' + args)
pubsub.unSubscribe(2)
}))
console.log(pubsub.subscribe('two', (...args) => { console.log('hello two:' + args) }))
console.log(pubsub.subscribe('two', (...args) => { console.log('hello two:' + args) }))
console.log(pubsub.subscribe('two', (...args) => { console.log('hello two:' + args) }))
console.log(pubsub.subscribe('two', (...args) => { console.log('hello two:' + args) }))
pubsub.publish('test', 'huang', 'biao', 18)
setTimeout(() => {
pubsub.publish('two', 'huang', 'biao', 18)
}, 2000)
}
testAction()
Vue的事件管理函式$on && $emit && $off
模擬事件管理器
function EventEmitter () {
let emitter = {
// 快取列表
list: {},
// 訂閱
on (event, fn) {
let _this = this;
// 如果物件中沒有對應的 event 值,也就是說明沒有訂閱過,就給 event 建立個快取列表
// 如有物件中有相應的 event 值,把 fn 新增到對應 event 的快取列表裡
(_this.list[event] || (_this.list[event] = [])).push(fn)
return _this
},
// 監聽一次
once (event, fn) {
// 先繫結,呼叫後刪除
let _this = this
function on () {
_this.off(event, on)
fn.apply(_this, arguments)
}
on.fn = fn
_this.on(event, on)
return _this
},
// 取消訂閱
off (event, fn) {
let _this = this
let fns = _this.list[event]
// 如果快取列表中沒有相應的 fn,返回false
if (!fns) return false
if (!fn) {
// 如果沒有傳 fn 的話,就會將 event 值對應快取列表中的 fn 都清空
fns && (fns.length = 0)
} else {
// 若有 fn,遍歷快取列表,看看傳入的 fn 與哪個函式相同,如果相同就直接從快取列表中刪掉即可
let cb
for (let i = 0, cbLen = fns.length; i < cbLen; i++) {
cb = fns[i]
if (cb === fn || cb.fn === fn) {
fns.splice(i, 1)
break
}
}
}
return _this
},
// 釋出
emit () {
let _this = this
// 第一個引數是對應的 event 值,直接用陣列的 shift 方法取出
let event = [].shift.call(arguments)
let fns = [..._this.list[event]]
// 如果快取列表裡沒有 fn 就返回 false
if (!fns || fns.length === 0) {
return false
}
// 遍歷 event 值對應的快取列表,依次執行 fn
fns.forEach(fn => {
fn.apply(_this, arguments)
})
return _this
}
}
return emitter
}
function user1 (...content) {
console.log('使用者1訂閱了:', content)
}
function user2 (...content) {
console.log('使用者2訂閱了:', content)
}
function user3 (...content) {
console.log('使用者3訂閱了:', content)
}
function user4 (...content) {
console.log('使用者4訂閱了:', content)
}
function testAction () {
let eventEmitter = EventEmitter()
// 訂閱
eventEmitter.on('article1', user1)
eventEmitter.on('article1', user2)
eventEmitter.on('article1', user3)
// 取消user2方法的訂閱
eventEmitter.off('article1', user2)
eventEmitter.once('article2', user4)
// 釋出
eventEmitter.emit('article1', 'Javascript 釋出-訂閱模式', 'param1', 'param2')
eventEmitter.emit('article1', 'Javascript 釋出-訂閱模式', 'param1', 'param2')
eventEmitter.emit('article2', 'Javascript 觀察者模式', 'param1', 'param2')
eventEmitter.emit('article2', 'Javascript 觀察者模式', 'param1', 'param2')
}
testAction()
// eventEmitter.on('article1', user3).emit('article1', 'test111');
/*
使用者1訂閱了: [ 'Javascript 釋出-訂閱模式', 'param1', 'param2' ]
使用者3訂閱了: [ 'Javascript 釋出-訂閱模式', 'param1', 'param2' ]
使用者1訂閱了: [ 'Javascript 釋出-訂閱模式', 'param1', 'param2' ]
使用者3訂閱了: [ 'Javascript 釋出-訂閱模式', 'param1', 'param2' ]
使用者4訂閱了: [ 'Javascript 觀察者模式', 'param1', 'param2' ]
*/
釋出-訂閱模式與觀察者模式的區別
觀察者模式:觀察者(Observer)直接訂閱(Subscribe)主題(Subject),而當主題被啟用的時候,會觸發(Fire Event)觀察者裡的事件。
釋出訂閱模式:訂閱者(Subscriber)把自己想訂閱的事件註冊(Subscribe)到排程中心(Event Channel),當釋出者(Publisher)釋出該事件(Publish Event)到排程中心,也就是該事件觸發時,由排程中心統一排程(Fire Event)訂閱者註冊到排程中心的處理程式碼。
差異:
- 在觀察者模式中,觀察者是知道 Subject 的,Subject 一直保持對觀察者進行記錄。然而,在釋出訂閱模式中,釋出者和訂閱者不知道對方的存在。它們只有通過訊息代理進行通訊。
- 在釋出訂閱模式中,元件是鬆散耦合的,正好和觀察者模式相反。
- 觀察者模式大多數時候是同步的,比如當事件觸發,Subject 就會去呼叫觀察者的方法。而釋出-訂閱模式大多數時候是非同步的(使用訊息佇列)。
- 觀察者模式需要在單個應用程式地址空間中實現,而釋出-訂閱更像交叉應用模式。
相關文章
- js 觀察者模式 訂閱釋出模式JS模式
- JavaScript 觀察者 (釋出/訂閱) 模式JavaScript模式
- JavaScript設計模式 觀察者模式(釋出訂閱)JavaScript設計模式
- 觀察者模式-訂閱釋出模式模式
- 觀察者模式和釋出訂閱模式(上)模式
- 談談觀察者模式和釋出訂閱模式模式
- 觀察者模式 vs 釋出訂閱模式模式
- 觀察者模式 vs 釋出-訂閱模式模式
- 觀察者模式(又叫釋出-訂閱模式)模式
- 觀察者模式與釋出訂閱模式區別 - JS模式JS
- 理解javascript觀察者模式(訂閱者與釋出者)JavaScript模式
- JavaScript 設計模式之觀察者模式與釋出訂閱模式JavaScript設計模式
- 釋出訂閱 VS 觀察者模式模式
- JavaScript設計模式之釋出-訂閱模式(觀察者模式)-Part2JavaScript設計模式
- 設計模式學習之觀察者模式和釋出訂閱模式設計模式
- 設計模式(三)觀察者模式Observer(釋出訂閱)設計模式Server
- 淺談觀察者模式和釋出訂閱者模式的微妙區別模式
- 對於觀察者模式和釋出者-訂閱者模式的一些理解模式
- JS訂閱釋出模式JS模式
- Javascript(七)釋出-訂閱模式JavaScript模式
- 【10分鐘帶你瞭解釋出訂閱和觀察者模式】模式
- js設計模式--釋出訂閱模式JS設計模式
- JavaScript設計模式系列--釋出訂閱模式JavaScript設計模式
- Javascript設計模式之釋出-訂閱模式JavaScript設計模式
- JS設計模式七:釋出-訂閱模式JS設計模式
- SpringBoot事件監聽機制及觀察者模式/釋出訂閱模式Spring Boot事件模式
- 設計模式之釋出訂閱模式(2) Redis 釋出/訂閱模式設計模式Redis
- javascript設計模式 之 5 釋出-訂閱模式JavaScript設計模式
- 釋出-訂閱模式模式
- 釋出訂閱模式模式
- js進階-設計模式: 釋出訂閱模式JS設計模式
- JavaScript中釋出/訂閱模式的理解JavaScript模式
- 不好意思,觀察者模式跟釋出訂閱模式就是不一樣模式
- 5分鐘通過一個例子理解觀察者模式和釋出訂閱模式的區別模式
- 對釋出-訂閱者模式的解析模式
- javascript中的設計模式之釋出-訂閱模式JavaScript設計模式
- 非父子元件之間傳值(Bus/匯流排/釋出訂閱模式/觀察者模式)元件模式
- 設計模式之釋出訂閱模式(1) 一文搞懂釋出訂閱模式設計模式