前言
本系列文章主要根據《JavaScript設計模式與開發實踐》整理而來,其中會加入了一些自己的思考。希望對大家有所幫助。
文章系列
概念
釋出—訂閱模式又叫觀察者模式,它定義物件間的一種一對多的依賴關係,當一個物件的狀 態發生改變時,所有依賴於它的物件都將得到通知。
場景
DOM事件
document.body.addEventListener('click', function () {
alert(2);
}, false);
document.body.addEventListener('click', function () {
alert(3);
}, false);
document.body.addEventListener('click', function () {
alert(4);
}, false);
document.body.click(); // 模擬使用者點選
複製程式碼
優缺點
優點:釋出—訂閱模式的優點非常明顯,一為時間上的解耦,二為物件之間的解耦。 缺點:建立訂閱者本身要消耗一定的時間和記憶體,而 且當你訂閱一個訊息後,也許此訊息最後都未發生,但這個訂閱者會始終存在於記憶體中。
例子
銷售處訂閱房源
簡單的釋出訂閱
var Event = function() {
this.list = []
}
Event.prototype.add = function(listener) {
this.list.push(listener)
}
Event.prototype.triggle = function() {
this.list.forEach(listener => {
listener()
})
}
var event = new Event()
event.add(()=>{console.log('房源1--80平--200萬')})
event.add(()=>{console.log('房源2--200平--1000萬')})
event.triggle()
複製程式碼
或者
var event = {
list: [],
add(listener) {
this.list.push(listener)
},
triggle() {
this.list.forEach(listener => {
listener()
})
}
}
event.add(()=>{console.log('房源1--80平--200萬')})
event.add(()=>{console.log('房源2--200平--1000萬')})
event.triggle()
複製程式碼
但這種不能區分是發不了什麼訊息,比如有兩群人:訂閱80平房源報價和訂閱200瓶房源報價的人,這裡兩群人都會得到通知
改進
var event = {
list: {},
add(type, listener) {
if (!this.list[type]) {
this.list[type] = []
}
this.list[type].push(listener)
},
triggle(type) {
this.list[type] && this.list[type].forEach(listener => {
listener()
})
}
}
event.add('80平', ()=>{console.log('房源1--80平--200萬')})
event.add('80平', ()=>{console.log('房源2--80平--300萬')})
event.add('200平', ()=>{console.log('房源2--200平--1000萬')})
event.triggle('80平')
複製程式碼
這裡還少了一個取消訂閱的功能
增加取消訂閱
var event = {
list: {},
add(type, listener) {
if (!this.list[type]) {
this.list[type] = []
}
this.list[type].push(listener)
},
triggle(type) {
this.list[type] && this.list[type].forEach(listener => {
listener()
})
},
remove(type, fn) {
if (!this.list[type]) return
var index = this.list[type].findIndex(listener => listener === fn)
this.list[type].splice(index, 1)
}
}
var f1 = ()=>{console.log('房源1--80平--200萬')}
var f2 = ()=>{console.log('房源2--80平--300萬')}
var f3 = ()=>{console.log('房源2--200平--1000萬')}
event.add('80平', f1)
event.add('80平', f2)
event.add('200平', f3)
event.remove('80平', f2)
event.triggle('80平') // 房源1--80平--200萬
複製程式碼
上面程式碼結構還不是很清晰,我們再模擬銷售部真實的場景
更真實的銷售部場景
- 銷售部
- 銷售部有很多房源,如80平的,100平的等
- 客戶可以到銷售部登記自己想買的房源面積,並留下姓名。到時候如果有房源,銷售部就會通知客戶
- 客戶由於一些原因決定不買房的時候,可以取消訂閱
- 客戶
- 當有房源時,客戶有一個接聽報價的方法
var Event = function () {
this.list = {}
}
Event.prototype.add = function (area, client) {
if (!this.list[area]) this.list[area] = []
this.list[area].push(client)
}
Event.prototype.remove = function (area, client) {
if (!this.list[area]) return
var index = this.list[area].findIndex(item => item === client)
this.list[area].splice(index, 1)
}
Event.prototype.triggle = function (area, price) {
if (!this.list[area]) return
this.list[area].forEach(client => {
client.listen(area, price)
})
}
var Client = function (name) {
this.name = name
}
Client.prototype.listen = function (area, price) {
console.log(`${this.name}收到${area}平的房源報價${price}`)
}
var client1 = new Client('client1')
var client2 = new Client('client2')
var event = new Event()
event.add('80平', client1)
event.add('100平', client1)
event.add('80平', client2)
event.add('300平', client1)
event.remove('300平', client1)
event.triggle('80平', 200) // client1收到80平平的房源報價200 client2收到80平平的房源報價200
event.triggle('100平', 500) // client1收到100平平的房源報價500
event.triggle('200平', 1000) //
event.triggle('300平', 1000) //
```
上面的程式碼雖然已經很好了,但是還是有一個缺點:訂閱者接收不到訂閱之前釋出的訊息,如下客戶3也想訂閱80平的房源,但他收不到任何訊息
```js
var client3 = new Client('client3')
event.add('80平', client3)
```
我們希望客戶3也能收到訊息
### 必須先訂閱再發布嗎
我們增加一個cache欄位來記錄歷史房源報價
```js
var Event = function () {
this.list = {}
this.cache = {}
}
Event.prototype.add = function (area, client) {
if (!this.list[area]) this.list[area] = []
this.list[area].push(client)
this.cache[area].forEach(price => {
client.listen(area, price)
})
}
Event.prototype.remove = function (area, client) {
if (!this.list[area]) return
var index = this.list[area].findIndex(item => item === client)
this.list[area].splice(index, 1)
}
Event.prototype.triggle = function (area, price) {
if (!this.cache[area]) this.cache[area] = []
this.cache[area].push(price)
if (!this.list[area]) return
this.list[area].forEach(client => {
client.listen(area, price)
})
}
var Client = function (name) {
this.name = name
}
Client.prototype.listen = function (area, price) {
console.log(`${this.name}收到${area}平的房源報價${price}`)
}
var client1 = new Client('client1')
var client2 = new Client('client2')
var event = new Event()
// event.add('80平', client1)
// event.add('100平', client1)
// event.add('80平', client2)
// event.add('300平', client1)
// event.remove('300平', client1)
event.triggle('80平', 200) // client1收到80平平的房源報價200 client2收到80平平的房源報價200
event.triggle('100平', 500) // client1收到100平平的房源報價500
event.triggle('200平', 1000) //
event.triggle('300平', 1000) //
var client3 = new Client('client3')
event.add('80平', client3)
event.add('100平', client3)
```
## 網站登入
假如我們正在開發一個商城網站,網站裡有 header 頭部、nav 導航、訊息列表、購物車等模組。這幾個模組的渲染有一個共同的前提條件,就是必須先用 ajax 非同步請求獲取使用者的登入資訊。
這裡留給讀者自己實現複製程式碼