JS设计模式之发布-订阅模式

目录

概念

它定义了一种一对多的关系,让多个订阅者对象同时监听一个发布者。这个发布者的状态发生变化时就会通知所有订阅自己的订阅者对象,使它们能够自动更新自己

举例:

我们关注了一个公众号。那么,关注这个公众号的所有人都会收到公众号的推送。
这就是一个典型的发布-订阅模式。我们订阅了公众号,公众号有消息发布出来,我们都会收到。这里,我们是订阅者,公众号是发布者。

特点:

  • 我们(订阅者)只需要关注一下公众号(发布者),就可以在某个时间接收公众号(发布者)的推送,不需要一直询问(轮询)是否有新消息;
  • 公众号后台有一个订阅者的列表,发送消息时会根据列表发送给关注的人。当订阅者增加或减少时,列表会进行相应的改变;
  • 一个人可以关注多个公众号;不同公众号产生消息时,会相应的通知订阅了不同类型消息的人;

代码实现:

1. 事件监听函数 addEventListener

window.addEventListener('load', function() {
    console.log('load')
})

这段代码很常见,我们经常将一些操作挂载在onload上,当页面元素资源加载完毕,就会触发onload事件上的回调。
这也是一种发布-订阅模式,我们无法预知页面何时加载完毕,但可以通过windowonload事件,它会在加载完毕时向订阅者发布消息(即执行回调函数)

2. 使用 jQuery自带的API( on, trigger, off)来实现事件的订阅,发布,取消;

function eventHandler() {
    console.log('您订阅的消息来喽!')
}
// 事件订阅
$('#app').on('myEvent', eventHandler)
// 发布
$('#app').trigger('myEvent')
// 输出:您订阅的消息来喽!

// 取消订阅
$('#app').off('myEvent')
$('#app').trigger('myEvent')
// 无输出

3. 原生方式

function eventHandler() {
    console.log('您订阅的消息来喽!')
}

var app = document.getElementById('app')
// 事件订阅
app.addEventListener('myEvent', eventHandler)
// 发布
app.dispatchEvent(new Event('myEvent'))
// 输出:您订阅的消息来喽!

// 取消订阅
app.removeEventListener('myEvent',eventHandler)
app.dispatchEvent(new Event('myEvent'))
// 无输出

4. 用JS自己实现一下

案例:一家零食小铺,“吃货们”发现“绝味鸭脖”等零食售罄了!于是留了个电话给店主,店主给记在小本本上,等有货的时候,再通过小本本打电话通知“吃货们”

class eventHandler{
    constructor() {
        this.list = {}
    }

    // 消息订阅
    subscribe(type, fn) {
        if(this.list[type]){
            if(!this.list[type].includes(fn)){
                this.list[type].push(fn)
            }
        }else{
            this.list[type]=[fn]
        }
    }

    // 消息退订
    unsubscribe(type, fn) {
        if(!this.list[type] || !this.list[type].includes(fn))return
        let index = this.list[type].indexOf(fn)
        this.list[type].splice(index,1)

    }

    // 消息发布
    notify(type, info){
        if(!this.list[type])return
        this.list[type].forEach(fn => fn(info))
    }
}

const shop = new eventHandler()

// 小白先生和大黄女士 预订绝味鸭脖
shop.subscribe('绝味鸭脖', message => console.log('小白先生——' + message)) 
shop.subscribe('绝味鸭脖', f1 = message => console.log('大黄女士——' + message)) 
// 翠花 预订泡椒凤爪
shop.subscribe('泡椒凤爪', message => console.log('翠花——' + message)) 

console.log('进货了,打电话通知买家:')
shop.notify('绝味鸭脖','绝味鸭脖进货了!')
shop.notify('泡椒凤爪','泡椒凤爪进货了!')

console.log('绝味鸭脖卖完了,通知买家:')
shop.notify('绝味鸭脖','绝味鸭脖卖完了!')

console.log('没买到鸭脖的大黄,愤怒的退订了')
shop.unsubscribe('绝味鸭脖', f1)

console.log('进货了,打电话通知买家:')
shop.notify('绝味鸭脖','绝味鸭脖进货了!')

输出:
在这里插入图片描述

5. Vue的 EventBus

vue有父子组件通信,兄弟组件通信。
父子组件通信中:父组件通过props向下传递数据给子组件,子组件可以通过$emit事件通知父组件;
兄弟组件通信中:如果不使用Vuex来共享数据,那我们可以使用vue中的事件总线EventBus来通信
代码示例:
组件B发送消息给组件A
创建event-bus.js文件:

// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()

组件A使用:

// 组件A
import { EventBus } from "./event-bus.js";

EventBus.$on("myevent", args => {
		console.log(args)
})

组件B使用:

// 组件B
import { EventBus } from "./event-bus.js";

EventBus.$emit("myevent", 'some args')

6. vue源码中的发布-订阅模式

vue利用发布-订阅模式来实现数据层和视图层的双向绑定
在这里插入图片描述
组件渲染函数(Component Render Function)被执行前,会对数据层的数据进行响应式化。响应式化大致就是使用 Object.defineProperty 把数据转为 getter/setter,并为每个数据添加一个订阅者列表的过程。这个列表是getter闭包中的属性,将会记录所有依赖这个数据的组件。

每个组件都对应一个 Watcher订阅者。当每个组件的渲染函数被执行时,都会将本组件的 Watcher 放到自己所依赖的响应式数据的订阅者列表里,这就相当于完成了订阅,一般这个过程被称为依赖收集(Dependency Collect

当响应式数据发生变化的时候,也就是触发了 setter时,setter会负责通知(Notify)该数据的订阅者列表里的 WatcherWatcher会触发组件重渲染(Trigger re-render)来更新(update)视图

vue源码如下,大致了解一下:

// src/core/observer/index.js
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val // 如果原本对象拥有getter方法则执行
      if (Dep.target) {
        dep.depend()   // 进行依赖收集,dep.addSub
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)  // 如果原本对象拥有setter方法则执行
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()  // 如果发生变更,则通知更新
    }
  })

上面的dep.depend()dep.notify() 是订阅和发布的具体方法
简单来说,响应式数据是消息的发布者,而视图层是消息的订阅者,如果数据更新了,那么发布者会发布数据更新的消息来通知视图更新,从而实现数据层和视图层的双向绑定。
在这里插入图片描述

发布-订阅模式的优缺点

优点:实现解耦

  • 时间方面,注册的订阅行为由消息的发布方来决定何时调用,订阅者不用持续关注,当消息发生时发布者会负责通知;
  • 对象方面,发布者无需知道消息的接受者是谁,只需遍历订阅列表发送消息即可

缺点:

  • 消耗内存,创建结构和缓存订阅者会消耗计算和内存资源
  • 复杂:多个订阅者和发布者层层嵌套,使得程序难以追踪和调试

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_40693643/article/details/105062699
今日推荐