JS案例:Observer Pattern(观察者模式)和Publisher-Subscriber Pattern(发布者/订阅者模式)

简介: JS案例:Observer Pattern(观察者模式)和Publisher-Subscriber Pattern(发布者/订阅者模式)

发布/订阅模式和观察者模式一样吗?

在许多地方我们都能见到基于这二者或者说基于某种设计模式的框架,函数或插件


在浏览器中使用addEventListener(type,fn)对dom元素进行事件委托,事件监听用户的异步操作

Android中也有一个事件发布/订阅的轻量级框架:EventBus,原理与web相似

Socket.io的许多方法也是基于此类模式,监听与触发事件,批量广播等

在Node中同样也有一个events事件触发器解决异步操作的同步响应


那么其二者有什么区别吗,下面两张图可以简单描述他们的过程(发布者/订阅者模式我直接用事件的侦听(addeventlistener)与事件的派遣(dispatchevent)来形容帮助理解)

1.png1.png


Observer Pattern(观察者模式):

Subject(主题,或者叫被观察者):当状态发送变化时,需要通知队列中关联对象

Observer(观察者):当Subject发送消息时,通过回调获得信息

Observer(观察者)将事件(记做fn回调)丢给Subject(被观察者),然后就开始监视着他的一举一动,当Subject(被观察者)的(异步)任务完成后,同步触发事件fn回调将消息传输给Observer(观察者)后,完成一个完整的周期


实现过程:

Observer.js:

module.exports = class Observer { //定义观察者类,每个实例化后的观察者拥有订阅(subscribe)功能
    constructor() {}
    /**
     * 订阅
     * @param target 被观察者Subject的实例对象
     * @param fn 订阅注册的回调
     */
    subscribe(target, fn) {
        target.observerList.push(fn)
    }
}

Subject.js:

module.exports = class Subject { //定义被观察者类,每个实例化后拥有注册的观察者回调的列表(observerList)和触发回调(fireEvent)功能
    constructor() {
        this.observerList = []
    }
    /**
     * 触发
     * @param e 被观察者传递给观察者的参数
     */
    fireEvent(e) {
        this.observerList.forEach(item => {
            item(e)
        })
    }
}


main.js(使用场景)

const Observer = require('./js/observer');
const Subject = require('./js/subject')
class MyObserver extends Observer {}
class MySubject extends Subject {}
// 实例化两个观察者,同时对一个subject进行监听
const observer = new MyObserver()
const observer2 = new MyObserver()
const subject = new MySubject()
observer.subscribe(subject, (e) => {
    console.log(e) //hello world
})
observer2.subscribe(subject, (e) => {
    console.log(e) //hello world
})
// 延时激活观察者注册的函数,传递参数
setTimeout(subject.fireEvent.bind(subject, 'hello world'), 1000)


Publisher-Subscriber Pattern(发布者/订阅者模式):

Subscriber(订阅者):将事件注册到事件调度中心(Event Channel或者可以看做EventBus(事件总线))

Publisher(发布者):触发调度中心的事件

Event Channel(调度中心),与Vue和Android中的EventBus(事件总线)相似:得到Publisher(发布者)的消息后,统一处理Subscriber(订阅者)注册的事件

Subscriber(订阅者)通过on将事件注册到Event Channel(调度中心),并与Event Channel通过回调进行数据传递,当Subscriber(订阅者)触发Event Channel(调度中心)的事件并将数据传递至其中时,调度中心会激活之前与Subscriber(订阅者)建立的联系,通过emit发送数据,订阅者收到数据后完成一个周期


实现过程:

eventBus.js

// 发布/订阅设计模式(Pub/Sub)
class EventBus {
    constructor() {
        this._eventList = {} //调度中心列表
    }
    static Instance() { //返回当前类的实例的单例
        if (!EventBus._instance) {
            Object.defineProperty(EventBus, "_instance", {
                value: new EventBus()
            });
        }
        return EventBus._instance;
    }
    /**
     * 注册事件至调度中心
     * @param type 事件类型,特指具体事件名
     * @param fn 事件注册的回调
     */
    onEvent(type, fn) { //订阅者
        if (!this.isKeyInObj(this._eventList, type)) { //若调度中心未找到该事件的队列,则新建某个事件列表(可以对某个类型的事件注册多个回调函数)
            Object.defineProperty(this._eventList, type, {
                value: [],
                writable: true,
                enumerable: true,
                configurable: true
            })
        }
        this._eventList[type].push(fn)
    }
    /**
     * 触发调度中心的某个或者某些该事件类型下注册的函数
     * @param type 事件类型,特指具体事件名
     * @param data 发布者传递的参数
     */
    emitEvent(type, data) { //发布者
        if (this.isKeyInObj(this._eventList, type)) {
            for (let i = 0; i < this._eventList[type].length; i++) {
                this._eventList[type][i] && this._eventList[type][i](data)
            }
        }
    }
    offEvent(type, fn) { //销毁监听
        for (let i = 0; i < this._eventList[type].length; i++) {
            if (this._eventList[type][i] && this._eventList[type][i] === fn) {
                this._eventList[type][i] = null
            }
        }
    }
    /**
     * 检查对象是否包含该属性,除原型链
     * @param obj 被检查对象
     * @param key 被检查对象的属性
     */
    isKeyInObj(obj, key) {
        if (Object.hasOwnProperty.call(obj, key)) {
            return true
        }
        return false
    }
}
module.exports = EventBus.Instance()


main.js

const EventBus = require('./js/eventBus')
let list = [], //记录异步操作
    count = 0, //计数器
    timeTick = setInterval(function () {
        if (count++ > 3) { //当执行到一定时间时,销毁事件、定时器
            EventBus.offEvent('finish', eventHandler)
            clearInterval(timeTick)
        }
        list.push(count)
        EventBus.emitEvent('finish', {
            list
        })
    }, 1000)
EventBus.onEvent('finish', eventHandler)
function eventHandler(e) {
    console.log(e)
    // { list: [ 1 ] }
    // { list: [ 1, 2 ] }
    // { list: [ 1, 2, 3 ] }
    // { list: [ 1, 2, 3, 4 ] }
}

总结:发布者/订阅者模式实际上是基于观察者模式上优化实现的,然而其二者的区别还是有的


观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

优点:观察者和被观察者是抽象耦合的,其二者建立了一套触发机制,松耦合

缺点:二者之间循环依赖,如果关系复杂,如观察者数量过多,还是会造成性能问题,解决方式是避免同步执行造成线程阻塞


发布者/订阅者模式:与观察者模式类似,但是核心区别是发布者与订阅者互相无耦合,并不知道通知与被通知的对方的具体身份,而是将注册的函数放在统一的调度中心进行管理

优点:发布者/订阅者完全解耦,可扩展性高,常应用在分布式,紧耦合服务中

缺点:发布者解耦订阅者,这点既是主要优点,亦是缺点,打个比方,在Socket中,倘若服务端发送消息给客户端,不会在意是否发送成功,此时需要客户端返回接收到了消息才能算是保证了代码的可靠性和可用性


相关源码:码云地址


相关文章
|
1月前
Next.js 实战 (三):优雅的实现暗黑主题模式
这篇文章介绍了在Next.js中实现暗黑模式的具体步骤。首先,需要安装next-themes库。然后,在/components/ThemeProvider/index.tsx文件中新增ThemeProvider组件,并在/app/layout.tsx文件中注入该组件。如果想要加入过渡动画,可以修改代码实现主题切换时的动画效果。最后,需要在需要的位置引入ThemeModeButton组件,实现暗黑模式的切换。
|
2月前
|
前端开发 JavaScript UED
探索JavaScript的异步编程模式
【10月更文挑战第40天】在JavaScript的世界里,异步编程是一道不可或缺的风景线。它允许我们在等待慢速操作(如网络请求)完成时继续执行其他任务,极大地提高了程序的性能和用户体验。本文将深入浅出地探讨Promise、async/await等异步编程技术,通过生动的比喻和实际代码示例,带你领略JavaScript异步编程的魅力所在。
36 1
|
3月前
|
前端开发 JavaScript UED
探索JavaScript中的异步编程模式
【10月更文挑战第21天】在数字时代的浪潮中,JavaScript作为一门动态的、解释型的编程语言,以其卓越的灵活性和强大的功能在Web开发领域扮演着举足轻重的角色。本篇文章旨在深入探讨JavaScript中的异步编程模式,揭示其背后的原理和实践方法。通过分析回调函数、Promise对象以及async/await语法糖等关键技术点,我们将一同揭开JavaScript异步编程的神秘面纱,领略其带来的非阻塞I/O操作的魅力。让我们跟随代码的步伐,开启一场关于时间、性能与用户体验的奇妙之旅。
|
2月前
|
前端开发 JavaScript UED
探索JavaScript的异步编程模式
【10月更文挑战第33天】在JavaScript的世界里,异步编程是提升应用性能和用户体验的关键。本文将带你深入理解异步编程的核心概念,并展示如何在实际开发中运用这些知识来构建更流畅、响应更快的Web应用程序。从回调函数到Promises,再到async/await,我们将一步步解锁JavaScript异步编程的秘密,让你轻松应对各种复杂的异步场景。
|
3月前
|
JavaScript 前端开发 API
探索Node.js中的异步编程模式
【10月更文挑战第4天】在JavaScript的世界中,异步编程是提升应用性能和用户体验的关键。本文将深入探讨Node.js中异步编程的几种模式,包括回调函数、Promises、async/await,并分享如何有效利用这些模式来构建高性能的后端服务。
|
3月前
|
JavaScript 前端开发 调度
探索Node.js中的异步编程模式
在Node.js的世界里,异步编程是核心。本文将带你深入了解异步编程的精髓,通过代码示例和实际案例分析,我们将一起掌握事件循环、回调函数、Promises以及async/await等关键概念。准备好迎接挑战,让你的Node.js应用飞起来!
|
3月前
|
JavaScript 前端开发 开发者
探索Node.js中的异步编程模式
【9月更文挑战第33天】在JavaScript的后端领域,Node.js凭借其非阻塞I/O和事件驱动的特性,成为高性能应用的首选平台。本文将深入浅出地探讨Node.js中异步编程的核心概念、Promise对象、Async/Await语法以及它们如何优化后端开发的效率和性能。
35 7
|
4月前
|
JavaScript 前端开发 开发者
探索Node.js中的异步编程模式
【9月更文挑战第15天】在Node.js的世界中,“一切皆异步”不仅是一句口号,更是其设计哲学的核心。本文将带你深入理解Node.js中异步编程的几种主要模式,包括经典的回调函数、强大的Promise对象、以及简洁的async/await结构。我们将通过实例代码来展示每种模式的使用方式和优缺点,帮助你更好地掌握Node.js异步编程的精髓。无论你是Node.js新手还是有一定经验的开发者,这篇文章都能给你带来新的启示和思考。让我们一起开启Node.js异步编程的探索之旅吧!
|
3月前
|
前端开发 JavaScript
JavaScript动态渲染页面爬取——CSS位置偏移反爬案例分析与爬取实战
JavaScript动态渲染页面爬取——CSS位置偏移反爬案例分析与爬取实战
46 0
|
4月前
|
JavaScript 前端开发 中间件
深入浅出Node.js中间件模式
【9月更文挑战第13天】本文将带你领略Node.js中间件模式的魅力,从概念到实战,一步步揭示如何利用这一强大工具简化和增强你的Web应用。我们将通过实际代码示例,展示中间件如何在不修改原有代码的情况下,为请求处理流程添加功能层。无论你是前端还是后端开发者,这篇文章都将为你打开一扇通往更高效、更可维护代码的大门。