又称观察者模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知
初步实现
- 指定发布者
- 给发布者添加一个缓存列表,用于存放订阅者的回调函数,以便通知订阅者
- 发布消息的时候,发布者遍历缓存列表,依次触发里面存放的订阅者的回调函数
```javascript
// 定义发布者
const pub = {
// 订阅者回调函数缓存列表
subCb: [],
// 添加订阅者
subAdd(cb) {
}, // 触发订阅者回调函数,通知订阅者 subNotify() {this.subCb.push(cb)
} }for(let i = 0, len = this.subCb.length; i < len; i++ ) {this.subCb[i].apply(this, arguments)}
// 小明订阅消息,需要商家通知手机的品牌
pub.subAdd(function (brand, price) {
console.log(品牌: ${brand},价格: ${price})
})
// 小红订阅消息,需要商家通知手机的品牌和价格
pub.subAdd(function (brand, price) {
console.log(品牌: ${brand},价格: ${price})
})
// 商家发布消息给订阅者 pub.subNotify(‘apple’, ‘5000’) pub.subNotify(‘huawei’, ‘4288’)
<a name="DiJW7"></a>#### 输出结果<a name="dbzps"></a>#### 思考问题- 如果小明只想订阅品牌为apple的手机,但是这种情况下发布者还会把品牌为huawei的消息推送给他,应该怎么解决?<a name="d5Dw4"></a>### 改进实现给订阅者设置对应的`key`值以标明他想订阅的内容```javascript// 定义发布者const pub = {// 订阅者回调函数缓存列表subCb: [],// 添加订阅者,并使用key值区分订阅者订阅的消息类型subAdd(key, cb) {if (!this.subCb[key]) {// 如果该类型消息还没有被订阅,则给该类消息创建一个缓存列表this.subCb[key] = []}this.subCb[key].push(cb)},subNotify() {let key = Array.prototype.shift.call(arguments) // 取出订阅的消息类型let keyCb = this.subCb[key] // 取出对应的订阅者回调函数集合if (!keyCb || keyCb.length === 0) {return false // 如果没有订阅该类型消息的则返回}for(let i = 0, len = keyCb.length; i < len; i++ ) {keyCb[i].apply(this, arguments)}}}// 小明订阅apple品牌的价格pub.subAdd('apple', function (price) {console.log(`品牌: apple,价格: ${price}`)})// 小红订阅huawei品牌的价格pub.subAdd('huawei', function (price) {console.log(`品牌: huawei,价格: ${price}`)})// 发布者发布消息给订阅者pub.subNotify('apple', '5000')pub.subNotify('huawei', '4288')
输出结果

- 对于小明,他只收到了品牌为apple的价格推送消息
- 对于小红,她只收到了品牌为huawei的价格推送消息
再次思考
加入小明还想订阅另一个发布者发布的消息,那应该怎么办呢?不可能再重新写一个发布者吧通用实现
将发布-订阅功能提取出来,放在一个单独的对象里
// 提取发布订阅-功能const pubAndSub = {// 订阅者列表subCb: [],// 添加订阅者subAdd(key, cb) {if (!this.subCb[key]) {this.subCb[key] = []}this.subCb[key].push(cb)},// 通知订阅者subNotify() {let key = Array.prototype.shift.call(arguments)let keyCb = this.subCb[key]if (!keyCb || keyCb.length === 0) {return false}for (let i = 0, len = keyCb.length; i < len; i++) {keyCb[i].apply(this, arguments)}}}// 定义一个给对象动态添加发布订阅功能的函数const installPubSubFn = function (obj) {for (let i in pubAndSub) {obj[i] = pubAndSub[i]}}// 定义发布者1const pub1 = {}// 添加发布-订阅功能installPubSubFn(pub1)// 添加订阅对象// 小明订阅apple品牌的价格pub1.subAdd('apple', function (price) {console.log(`品牌: apple,价格: ${price}`)})// 小红订阅huawei品牌的价格pub1.subAdd('huawei', function (price) {console.log(`品牌: huawei,价格: ${price}`)})// 发布者1发布消息给订阅者pub1.subNotify('apple', '5000')pub1.subNotify('huawei', '4288')
输出结果
继续思考
既然有添加订阅事件,那么应该也要有取消订阅事件?这样该如何实现
添加取消订阅事件
// 提取发布订阅-功能const pubAndSub = {// 订阅者列表subCb: [],// 添加订阅者subAdd(key, cb) {if (!this.subCb[key]) {this.subCb[key] = []}this.subCb[key].push(cb)},// 通知订阅者subNotify() {let key = Array.prototype.shift.call(arguments)let keyCb = this.subCb[key]if (!keyCb || keyCb.length === 0) {return false}for (let i = 0, len = keyCb.length; i < len; i++) {keyCb[i].apply(this, arguments)}},// 移除订阅者subRemove(key, cb) {let keyCb = this.subCb[key]// 如果key对应的消息没有人订阅,则直接返回if (!keyCb) {return false}// 若果没有传入具体的回调函数,表示需要取消key对应的订阅if (!cb) {keyCb && (keyCb.length = 0)} else {// 反向遍历订阅的回调函数列表for (let l = keyCb.length - 1; l >= 0; l--) {let _cb = keyCb[l]if (_cb === cb) {// 删除订阅者的回调函数keyCb.splice(l, 1)}}}}}// 定义一个给对象动态添加发布订阅功能的函数const installPubSubFn = function (obj) {for (let i in pubAndSub) {obj[i] = pubAndSub[i]}}// 定义发布者1const pub1 = {}// 添加发布-订阅功能installPubSubFn(pub1)// 添加订阅对象// 小明订阅apple品牌的价格pub1.subAdd('apple', function (price) {console.log(`品牌: apple,价格: ${price}`)})// 小红订阅huawei品牌的价格pub1.subAdd('huawei', function (price) {console.log(`品牌: huawei,价格: ${price}`)})// 小明取消订阅applepub1.subRemove('apple')// 小明增加订阅oppopub1.subAdd('oppo', function (price) {console.log(`品牌: oppo,价格: ${price}`)})// 发布者1发布消息给订阅者pub1.subNotify('apple', '5000') // apple取消了订阅,消息不会发布出去pub1.subNotify('huawei', '4288')pub1.subNotify('oppo', '3888') // 增加了oppo的订阅,订阅者收到了消息
输出结果

