又称观察者模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知

初步实现

  1. 指定发布者
  2. 给发布者添加一个缓存列表,用于存放订阅者的回调函数,以便通知订阅者
  3. 发布消息的时候,发布者遍历缓存列表,依次触发里面存放的订阅者的回调函数 ```javascript // 定义发布者 const pub = { // 订阅者回调函数缓存列表 subCb: [], // 添加订阅者 subAdd(cb) {
    1. this.subCb.push(cb)
    }, // 触发订阅者回调函数,通知订阅者 subNotify() {
    1. for(let i = 0, len = this.subCb.length; i < len; i++ ) {
    2. this.subCb[i].apply(this, arguments)
    3. }
    } }

// 小明订阅消息,需要商家通知手机的品牌 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’)

  1. <a name="DiJW7"></a>
  2. #### 输出结果
  3. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/400373/1589248823683-034dd343-cee9-4736-abf3-b254028fd796.png#align=left&display=inline&height=90&margin=%5Bobject%20Object%5D&name=image.png&originHeight=122&originWidth=417&size=23106&status=done&style=none&width=303)
  4. <a name="dbzps"></a>
  5. #### 思考问题
  6. - 如果小明只想订阅品牌为apple的手机,但是这种情况下发布者还会把品牌为huawei的消息推送给他,应该怎么解决?
  7. <a name="d5Dw4"></a>
  8. ### 改进实现
  9. 给订阅者设置对应的`key`值以标明他想订阅的内容
  10. ```javascript
  11. // 定义发布者
  12. const pub = {
  13. // 订阅者回调函数缓存列表
  14. subCb: [],
  15. // 添加订阅者,并使用key值区分订阅者订阅的消息类型
  16. subAdd(key, cb) {
  17. if (!this.subCb[key]) {
  18. // 如果该类型消息还没有被订阅,则给该类消息创建一个缓存列表
  19. this.subCb[key] = []
  20. }
  21. this.subCb[key].push(cb)
  22. },
  23. subNotify() {
  24. let key = Array.prototype.shift.call(arguments) // 取出订阅的消息类型
  25. let keyCb = this.subCb[key] // 取出对应的订阅者回调函数集合
  26. if (!keyCb || keyCb.length === 0) {
  27. return false // 如果没有订阅该类型消息的则返回
  28. }
  29. for(let i = 0, len = keyCb.length; i < len; i++ ) {
  30. keyCb[i].apply(this, arguments)
  31. }
  32. }
  33. }
  34. // 小明订阅apple品牌的价格
  35. pub.subAdd('apple', function (price) {
  36. console.log(`品牌: apple,价格: ${price}`)
  37. })
  38. // 小红订阅huawei品牌的价格
  39. pub.subAdd('huawei', function (price) {
  40. console.log(`品牌: huawei,价格: ${price}`)
  41. })
  42. // 发布者发布消息给订阅者
  43. pub.subNotify('apple', '5000')
  44. pub.subNotify('huawei', '4288')

输出结果

image.png

  • 对于小明,他只收到了品牌为apple的价格推送消息
  • 对于小红,她只收到了品牌为huawei的价格推送消息

    再次思考

    加入小明还想订阅另一个发布者发布的消息,那应该怎么办呢?不可能再重新写一个发布者吧

    通用实现

    将发布-订阅功能提取出来,放在一个单独的对象里

  1. // 提取发布订阅-功能
  2. const pubAndSub = {
  3. // 订阅者列表
  4. subCb: [],
  5. // 添加订阅者
  6. subAdd(key, cb) {
  7. if (!this.subCb[key]) {
  8. this.subCb[key] = []
  9. }
  10. this.subCb[key].push(cb)
  11. },
  12. // 通知订阅者
  13. subNotify() {
  14. let key = Array.prototype.shift.call(arguments)
  15. let keyCb = this.subCb[key]
  16. if (!keyCb || keyCb.length === 0) {
  17. return false
  18. }
  19. for (let i = 0, len = keyCb.length; i < len; i++) {
  20. keyCb[i].apply(this, arguments)
  21. }
  22. }
  23. }
  24. // 定义一个给对象动态添加发布订阅功能的函数
  25. const installPubSubFn = function (obj) {
  26. for (let i in pubAndSub) {
  27. obj[i] = pubAndSub[i]
  28. }
  29. }
  30. // 定义发布者1
  31. const pub1 = {}
  32. // 添加发布-订阅功能
  33. installPubSubFn(pub1)
  34. // 添加订阅对象
  35. // 小明订阅apple品牌的价格
  36. pub1.subAdd('apple', function (price) {
  37. console.log(`品牌: apple,价格: ${price}`)
  38. })
  39. // 小红订阅huawei品牌的价格
  40. pub1.subAdd('huawei', function (price) {
  41. console.log(`品牌: huawei,价格: ${price}`)
  42. })
  43. // 发布者1发布消息给订阅者
  44. pub1.subNotify('apple', '5000')
  45. pub1.subNotify('huawei', '4288')

输出结果

image.png

继续思考

既然有添加订阅事件,那么应该也要有取消订阅事件?这样该如何实现

添加取消订阅事件

  1. // 提取发布订阅-功能
  2. const pubAndSub = {
  3. // 订阅者列表
  4. subCb: [],
  5. // 添加订阅者
  6. subAdd(key, cb) {
  7. if (!this.subCb[key]) {
  8. this.subCb[key] = []
  9. }
  10. this.subCb[key].push(cb)
  11. },
  12. // 通知订阅者
  13. subNotify() {
  14. let key = Array.prototype.shift.call(arguments)
  15. let keyCb = this.subCb[key]
  16. if (!keyCb || keyCb.length === 0) {
  17. return false
  18. }
  19. for (let i = 0, len = keyCb.length; i < len; i++) {
  20. keyCb[i].apply(this, arguments)
  21. }
  22. },
  23. // 移除订阅者
  24. subRemove(key, cb) {
  25. let keyCb = this.subCb[key]
  26. // 如果key对应的消息没有人订阅,则直接返回
  27. if (!keyCb) {
  28. return false
  29. }
  30. // 若果没有传入具体的回调函数,表示需要取消key对应的订阅
  31. if (!cb) {
  32. keyCb && (keyCb.length = 0)
  33. } else {
  34. // 反向遍历订阅的回调函数列表
  35. for (let l = keyCb.length - 1; l >= 0; l--) {
  36. let _cb = keyCb[l]
  37. if (_cb === cb) {
  38. // 删除订阅者的回调函数
  39. keyCb.splice(l, 1)
  40. }
  41. }
  42. }
  43. }
  44. }
  45. // 定义一个给对象动态添加发布订阅功能的函数
  46. const installPubSubFn = function (obj) {
  47. for (let i in pubAndSub) {
  48. obj[i] = pubAndSub[i]
  49. }
  50. }
  51. // 定义发布者1
  52. const pub1 = {}
  53. // 添加发布-订阅功能
  54. installPubSubFn(pub1)
  55. // 添加订阅对象
  56. // 小明订阅apple品牌的价格
  57. pub1.subAdd('apple', function (price) {
  58. console.log(`品牌: apple,价格: ${price}`)
  59. })
  60. // 小红订阅huawei品牌的价格
  61. pub1.subAdd('huawei', function (price) {
  62. console.log(`品牌: huawei,价格: ${price}`)
  63. })
  64. // 小明取消订阅apple
  65. pub1.subRemove('apple')
  66. // 小明增加订阅oppo
  67. pub1.subAdd('oppo', function (price) {
  68. console.log(`品牌: oppo,价格: ${price}`)
  69. })
  70. // 发布者1发布消息给订阅者
  71. pub1.subNotify('apple', '5000') // apple取消了订阅,消息不会发布出去
  72. pub1.subNotify('huawei', '4288')
  73. pub1.subNotify('oppo', '3888') // 增加了oppo的订阅,订阅者收到了消息

输出结果

image.png

发布-订阅模式总结

优点

  • 时间上解耦
  • 对象之间解耦

    缺点

  • 增加消耗

    • 创建结构和缓存订阅者需要消耗计算和内存,即使订阅后没有被触发也还是订阅者还是会存在于内存中
  • 增加复杂度
    • 当有多个发布者和订阅者嵌套在一块,理解难度加大