likes
comments
collection
share

面试官:聊聊发布订阅模式

作者站长头像
站长
· 阅读数 34

什么是发布订阅?

我们在前端开发中会遇到很多种设计模式,例如MVC模式,MVVM模式,单例模式,观察者模式。那么发布订阅模式也是一种非常常见的模式之一。

发布订阅模式允许一个对象(发布者或者称为主题)发布事件,而其他对象(订阅者或者称为观察者)订阅这些事件,当事件发生时,发布者会通知所有订阅者进行相应的处理。这种模式常被用于事件驱动的架构中,如前端开发中的事件处理、消息队列等。

该模式包含三个核心组件:发布者、订阅者、事件。

  • 发布者:当发布者发布事件时,会通知所有订阅者,并调用订阅者的处理方法。
  • 订阅者:订阅者负责订阅事件或者消息,并提供处理事件的方法。当发布者发布相关事件时,订阅者会接收到通知并执行相应的处理逻辑。
  • 事件:事件是发布者和订阅者之间通信的载体,包含了事件类型和相关的数据。

应用场景

1. 事件处理

例如:前端开发中的事件监听和处理,通过发布订阅模式实现事件通知机制。

实例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            width: 100px;
            height: 100px;
            background: #000;
        }
    </style>
</head>
<body>
    <div id="box"></div>

    <script>
        let ev = new Event('look', { bubbles: true,cancelable:false }) //创建一个事件

        let box = document.getElementById('box')
        box.addEventListener('look', (e) => { //注册或订阅了一个事件,前提要发布
                console.log('在box上触发了look事件');
        })
        box.dispatchEvent(ev) //在box上发布look事件
    </script>
</body>
</html>

创建事件对象: 使用 new Event() 创建一个事件对象 ev。这里的 { bubbles: true, cancelable: false } 参数表示该事件可以冒泡但不可取消。

注册事件监听器(订阅): 使用 addEventListener() 方法注册事件监听器,监听特定类型的事件。

发布事件: 通过 box.dispatchEvent(ev)box 元素上发布了 look 事件,即触发了注册的事件监听器。

发布者是 box 元素,它发布了一个名为 look 的自定义事件。订阅者是通过 addEventListener() 注册在 box 上的事件监听器,当 box 发布 look 事件时,订阅者就会收到通知并执行相应的处理逻辑。

2. 处理异步

实例:

<script>
    let finish = new CustomEvent('finish', {detail: {name: 'ok'}})

    function fnA() {
        setTimeout(() => {
            console.log('请求A完成');
            window.dispatchEvent(finish)
        },1000)
    }
    function fnB() {
        setTimeout(() => {
            console.log('请求B完成');
        },500)
    }
    fnA()
    window.addEventListener('finish', () => {
        fnB()
    })
</script>
  1. fnA 函数在 setTimeout 中模拟了一个异步请求,1秒后请求完成,并打印 '请求A完成',然后派发了一个名为 finish 的自定义事件到 window 对象上。fnB 函数也在 setTimeout 中模拟了一个异步请求,500毫秒后请求完成,并打印 '请求B完成'。
  2. 使用 window.addEventListener 监听 finish 事件,当 finish 事件被派发时,执行 fnB(),开始执行请求B。

手写发布订阅

完整代码:

class EventEmitter {
    constructor() {
        this.event = {} // 'run': [cb]
    }
    on(type, cb) { //用于订阅事件
        if(!this.event[type]) {
            //没人订阅过
            this.event[type] = [cb]
        } else {
            //有人订阅过
            this.event[type].push(cb)
        }
    }
    once(type, cb) { //用于订阅一次事件
        const fn = (...args) => {
            cb(...args)
            this.off(type, fn)
        }
        this.on(type, fn)
    }
    emit(type, ...args) { //用于发布事件
        //使用类数组接收不定参数
        if (!this.event[type]) {
            return
        } else { //是有订阅者
            this.event[type].forEach(cb => {
                //在把类数组解构
                cb(...args)
            })
        }
    }
    off(type,cb) { //取消订阅事件
        if (!this.event[type]){
            return
        } else {
            this.event[type] = this.event[type].filter(item => item !== cb);
        }
    }
}

思路

先在构造函数中初始化了一个空对象 this.event,用于存储不同类型事件的回调函数列表。且每个类型事件的回调函数都用一个数组来存储。

1. 订阅者(on、once)

on

  • on 方法用于订阅事件,接收两个参数:事件类型 type 和回调函数 cb
  • 首先判断该事件类型是否已经有订阅者,如果没有则创建一个数组存储回调函数,并将回调函数添加到数组中;如果已经有订阅者,则直接将回调函数添加到对应的数组中。

once(订阅一次)

注意订阅一次指定是:在订阅一次后,当事件被发布并触发时,该订阅者的回调函数会被执行一次,然后自动取消对该事件的订阅,即使该事件被多次发布也不会再次触发该订阅者的回调函数。

once 方法与 on 方法类似,区别在于它只订阅一次事件。它创建了一个新的函数 fn,在执行回调函数 cb 后立即取消对该事件的订阅。

2. 发布者(emit)

  • emit 方法用于发布事件,接收两个参数:事件类型 type 和传递给回调函数的参数 args
  • 首先判断该事件类型是否有订阅者,如果没有则直接返回;如果有订阅者,则依次执行存储在事件列表中的所有回调函数,并传入参数 args

3. 取消订阅 off

  • off 方法用于取消订阅事件。
  • 首先判断该事件类型是否有订阅者,如果没有则直接返回;如果有订阅者,则使用 filter 方法过滤掉需要取消的回调函数,从而实现取消订阅。

最后

发布订阅模式可以帮助实现对象之间的解耦,使得不同部分的代码更灵活和可维护。

希望本文对你有帮助!

转载自:https://juejin.cn/post/7355670312396750898
评论
请登录