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中,倘若服务端发送消息给客户端,不会在意是否发送成功,此时需要客户端返回接收到了消息才能算是保证了代码的可靠性和可用性


相关源码:码云地址


相关文章
|
JavaScript 前端开发 Docker
如何通过pm2以cluster模式多进程部署next.js(包括docker下的部署)
通过这些步骤,可以确保您的Next.js应用在多核服务器上高效运行,并且在Docker环境中实现高效的容器化管理。
1412 44
|
JavaScript 前端开发 索引
40个JS常用使用技巧案例
大家好,我是V哥。在日常开发中,JS是解决页面交互的利器。V哥总结了40个实用的JS小技巧,涵盖数组操作、对象处理、函数使用等,并附带案例代码和解释。从数组去重到异步函数,这些技巧能显著提升开发效率。先赞再看后评论,腰缠万贯财进门。关注威哥爱编程,全栈开发就你行!
381 16
|
JavaScript 前端开发 Java
深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
Array.find() 是 JavaScript 数组方法中一个非常实用和强大的工具。它不仅提供了简洁的查找操作,还具有性能上的独特优势:返回的引用能够直接影响原数组的数据内容,使得数据更新更加高效。通过各种场景的展示,我们可以看到 Array.find() 在更新、条件查找和嵌套结构查找等场景中的广泛应用。 在实际开发中,掌握 Array.find() 的特性和使用技巧,可以让代码更加简洁高效,特别是在需要直接修改原数据内容的情形。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
监控 JavaScript 前端开发
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
MutationObserver 是一个非常强大的 API,提供了一种高效、灵活的方式来监听和响应 DOM 变化。它解决了传统 DOM 事件监听器的诸多局限性,通过异步、批量的方式处理 DOM 变化,大大提高了性能和效率。在实际开发中,合理使用 MutationObserver 可以帮助我们更好地控制 DOM 操作,提高代码的健壮性和可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
Next.js 实战 (三):优雅的实现暗黑主题模式
这篇文章介绍了在Next.js中实现暗黑模式的具体步骤。首先,需要安装next-themes库。然后,在/components/ThemeProvider/index.tsx文件中新增ThemeProvider组件,并在/app/layout.tsx文件中注入该组件。如果想要加入过渡动画,可以修改代码实现主题切换时的动画效果。最后,需要在需要的位置引入ThemeModeButton组件,实现暗黑模式的切换。
532 0
Next.js 实战 (三):优雅的实现暗黑主题模式
|
前端开发 JavaScript UED
探索JavaScript的异步编程模式
【10月更文挑战第40天】在JavaScript的世界里,异步编程是一道不可或缺的风景线。它允许我们在等待慢速操作(如网络请求)完成时继续执行其他任务,极大地提高了程序的性能和用户体验。本文将深入浅出地探讨Promise、async/await等异步编程技术,通过生动的比喻和实际代码示例,带你领略JavaScript异步编程的魅力所在。
160 1
|
前端开发 JavaScript UED
探索JavaScript中的异步编程模式
【10月更文挑战第21天】在数字时代的浪潮中,JavaScript作为一门动态的、解释型的编程语言,以其卓越的灵活性和强大的功能在Web开发领域扮演着举足轻重的角色。本篇文章旨在深入探讨JavaScript中的异步编程模式,揭示其背后的原理和实践方法。通过分析回调函数、Promise对象以及async/await语法糖等关键技术点,我们将一同揭开JavaScript异步编程的神秘面纱,领略其带来的非阻塞I/O操作的魅力。让我们跟随代码的步伐,开启一场关于时间、性能与用户体验的奇妙之旅。
|
JavaScript 前端开发 调度
探索Node.js中的异步编程模式
在Node.js的世界里,异步编程是核心。本文将带你深入了解异步编程的精髓,通过代码示例和实际案例分析,我们将一起掌握事件循环、回调函数、Promises以及async/await等关键概念。准备好迎接挑战,让你的Node.js应用飞起来!
223 5
|
JavaScript 前端开发 开发者
探索Node.js中的异步编程模式
【9月更文挑战第33天】在JavaScript的后端领域,Node.js凭借其非阻塞I/O和事件驱动的特性,成为高性能应用的首选平台。本文将深入浅出地探讨Node.js中异步编程的核心概念、Promise对象、Async/Await语法以及它们如何优化后端开发的效率和性能。
218 7
|
JavaScript 前端开发 API
探索Node.js中的异步编程模式
【10月更文挑战第4天】在JavaScript的世界中,异步编程是提升应用性能和用户体验的关键。本文将深入探讨Node.js中异步编程的几种模式,包括回调函数、Promises、async/await,并分享如何有效利用这些模式来构建高性能的后端服务。
138 2

热门文章

最新文章

下一篇
开通oss服务