精华内容
下载资源
问答
  • 主要介绍了JavaScript设计模式之观察者模式(发布订阅模式)原理与实现方法,结合实例形式分析了JavaScript观察者模式概念、原理、使用方法及相关操作注意事项,需要的朋友可以参考下
  • 今天的话题是javascript中常被提及的「发布订阅模式和观察者模式」,提到这,我不由得想起了一次面试。

    今天的话题是javascript中常被提及的「发布订阅模式和观察者模式」,提到这,我不由得想起了一次面试。记得在去年的一次求职面试过程中,面试官问我,“你在项目中是怎么处理非父子组件之间的通信的?”。我答道,“有用到vuex,有的场景也会用EventEmitter2”。面试官继续问,“那你能手写代码,实现一个简单的EventEmitter吗?”

    手写EventEmitter

    我犹豫了一会儿,想到使用EventEmitter2时,主要是用emit发事件,用on监听事件,还有off销毁事件监听者,removeAllListeners销毁指定事件的所有监听者,还有once之类的方法。考虑到时间关系,我想着就先实现发事件,监听事件,移除监听者这几个功能。当时可能有点紧张,不过有惊无险,在面试官给了一点提示后,顺利地写出来了!现在把这部分代码也记下来。

    class EventEmitter {
        constructor() {
            // 维护事件及监听者
            this.listeners = {}
        }
        /**
         * 注册事件监听者
         * @param {String} type 事件类型
         * @param {Function} cb 回调函数
         */
        on(type, cb) {
            if (!this.listeners[type]) {
                this.listeners[type] = []
            }
            this.listeners[type].push(cb)
        }
        /**
         * 发布事件
         * @param {String} type 事件类型
         * @param  {...any} args 参数列表,把emit传递的参数赋给回调函数
         */
        emit(type, ...args) {
            if (this.listeners[type]) {
                this.listeners[type].forEach(cb => {
                    cb(...args)
                })
            }
        }
        /**
         * 移除某个事件的一个监听者
         * @param {String} type 事件类型
         * @param {Function} cb 回调函数
         */
        off(type, cb) {
            if (this.listeners[type]) {
                const targetIndex = this.listeners[type].findIndex(item => item === cb)
                if (targetIndex !== -1) {
                    this.listeners[type].splice(targetIndex, 1)
                }
                if (this.listeners[type].length === 0) {
                    delete this.listeners[type]
                }
            }
        }
        /**
         * 移除某个事件的所有监听者
         * @param {String} type 事件类型
         */
        offAll(type) {
            if (this.listeners[type]) {
                delete this.listeners[type]
            }
        }
    }
    // 创建事件管理器实例
    const ee = new EventEmitter()
    // 注册一个chifan事件监听者
    ee.on('chifan', function() { console.log('吃饭了,我们走!') })
    // 发布事件chifan
    ee.emit('chifan')
    // 也可以emit传递参数
    ee.on('chifan', function(address, food) { console.log(`吃饭了,我们去${address}${food}!`) })
    ee.emit('chifan', '三食堂', '铁板饭') // 此时会打印两条信息,因为前面注册了两个chifan事件的监听者
    
    // 测试移除事件监听
    const toBeRemovedListener = function() { console.log('我是一个可以被移除的监听者') }
    ee.on('testoff', toBeRemovedListener)
    ee.emit('testoff')
    ee.off('testoff', toBeRemovedListener)
    ee.emit('testoff') // 此时事件监听已经被移除,不会再有console.log打印出来了
    
    // 测试移除chifan的所有事件监听
    ee.offAll('chifan')
    console.log(ee) // 此时可以看到ee.listeners已经变成空对象了,再emit发送chifan事件也不会有反应了
    

    有了这个自己写的简单版本的EventEmitter,我们就不用依赖第三方库啦。对了,vue也可以帮我们做这样的事情。

    const ee = new Vue();
    ee.$on('chifan', function(address, food) { console.log(`吃饭了,我们去${address}${food}!`) })
    ee.$emit('chifan', '三食堂', '铁板饭')
    

    所以我们可以单独new一个Vue的实例,作为事件管理器导出给外部使用。想测试的朋友可以直接打开vue官网,在控制台试试,也可以在自己的vue项目中实践下哦。

    发布订阅模式

    其实仔细看看,EventEmitter就是一个典型的发布订阅模式,实现了事件调度中心。发布订阅模式中,包含发布者,事件调度中心,订阅者三个角色。我们刚刚实现的EventEmitter的一个实例ee就是一个事件调度中心,发布者和订阅者是松散耦合的,互不关心对方是否存在,他们关注的是事件本身。发布者借用事件调度中心提供的emit方法发布事件,而订阅者则通过on进行订阅。

    如果还不是很清楚的话,我们把代码换下单词,是不是变得容易理解一点呢?

    class PubSub {
        constructor() {
            // 维护事件及订阅行为
            this.events = {}
        }
        /**
         * 注册事件订阅行为
         * @param {String} type 事件类型
         * @param {Function} cb 回调函数
         */
        subscribe(type, cb) {
            if (!this.events[type]) {
                this.events[type] = []
            }
            this.events[type].push(cb)
        }
        /**
         * 发布事件
         * @param {String} type 事件类型
         * @param  {...any} args 参数列表
         */
        publish(type, ...args) {
            if (this.events[type]) {
                this.events[type].forEach(cb => {
                    cb(...args)
                })
            }
        }
        /**
         * 移除某个事件的一个订阅行为
         * @param {String} type 事件类型
         * @param {Function} cb 回调函数
         */
        unsubscribe(type, cb) {
            if (this.events[type]) {
                const targetIndex = this.events[type].findIndex(item => item === cb)
                if (targetIndex !== -1) {
                    this.events[type].splice(targetIndex, 1)
                }
                if (this.events[type].length === 0) {
                    delete this.events[type]
                }
            }
        }
        /**
         * 移除某个事件的所有订阅行为
         * @param {String} type 事件类型
         */
        unsubscribeAll(type) {
            if (this.events[type]) {
                delete this.events[type]
            }
        }
    }
    

    画图分析

    最后,我们画个图加深下理解:

    发布订阅模式图解

    特点

    • 发布订阅模式中,对于发布者Publisher和订阅者Subscriber没有特殊的约束,他们好似是匿名活动,借助事件调度中心提供的接口发布和订阅事件,互不了解对方是谁。
    • 松散耦合,灵活度高,常用作事件总线
    • 易理解,可类比于DOM事件中的dispatchEventaddEventListener

    缺点

    • 当事件类型越来越多时,难以维护,需要考虑事件命名的规范,也要防范数据流混乱。

    观察者模式

    观察者模式与发布订阅模式相比,耦合度更高,通常用来实现一些响应式的效果。在观察者模式中,只有两个主体,分别是目标对象Subject,观察者Observer

    • 观察者需Observer要实现update方法,供目标对象调用。update方法中可以执行自定义的业务代码。
    • 目标对象Subject也通常被叫做被观察者或主题,它的职能很单一,可以理解为,它只管理一种事件。Subject需要维护自身的观察者数组observerList,当自身发生变化时,通过调用自身的notify方法,依次通知每一个观察者执行update方法。

    按照这种定义,我们可以实现一个简单版本的观察者模式。

    // 观察者
    class Observer {
        /**
         * 构造器
         * @param {Function} cb 回调函数,收到目标对象通知时执行
         */
        constructor(cb){
            if (typeof cb === 'function') {
                this.cb = cb
            } else {
                throw new Error('Observer构造器必须传入函数类型!')
            }
        }
        /**
         * 被目标对象通知时执行
         */
        update() {
            this.cb()
        }
    }
    
    // 目标对象
    class Subject {
        constructor() {
            // 维护观察者列表
            this.observerList = []
        }
        /**
         * 添加一个观察者
         * @param {Observer} observer Observer实例
         */
        addObserver(observer) {
            this.observerList.push(observer)
        }
        /**
         * 通知所有的观察者
         */
        notify() {
            this.observerList.forEach(observer => {
                observer.update()
            })
        }
    }
    
    const observerCallback = function() {
        console.log('我被通知了')
    }
    const observer = new Observer(observerCallback)
    
    const subject = new Subject();
    subject.addObserver(observer);
    subject.notify();
    

    画图分析

    最后也整张图理解下观察者模式:

    观察者模式

    特点

    • 角色很明确,没有事件调度中心作为中间者,目标对象Subject和观察者Observer都要实现约定的成员方法。
    • 双方联系更紧密,目标对象的主动性很强,自己收集和维护观察者,并在状态变化时主动通知观察者更新。

    缺点

    我还没体会到,这里不做评价

    结语

    关于这个话题,网上文章挺多的,观点上可能也有诸多分歧。重复造轮子,纯属帮助自己加深理解。

    本人水平有限,以上仅是个人观点,如有错误之处,还请斧正!如果能帮到您理解发布订阅模式和观察者模式,非常荣幸!

    如果有兴趣看看我这糟糕的代码,请点击github,祝大家生活愉快!

    展开全文
  • 发布订阅模式与观察者模式的代码实例观察者模式观察者模式的实现发布订阅模式发布订阅模式的优点发布订阅模式的缺点发布订阅模式的实现观察者模式VS发布订阅模式 观察者模式 所谓观察者模式,其实就是为了实现松耦合...

    观察者模式

    所谓观察者模式,其实就是为了实现松耦合(loosely coupled)。

    用《Head First设计模式》里的气象站为例子,每当气象测量数据有更新,changed()方法就会被调用,于是我们可以在changed()方法里面,更新气象仪器上的数据,比如温度、气压等等。

    但是这样写有个问题,就是如果以后我们想在changed()方法被调用时,更新更多的信息,比如说湿度,那就要去修改changed()方法的代码,这就是紧耦合的坏处。

    怎么解决呢?使用观察者模式,面向接口编程,实现松耦合。

    观察者模式里面,changed()方法所在的实例对象,就是被观察者(Subject,或者叫Observable),它只需维护一套观察者(Observer)的集合,这些Observer实现相同的接口,Subject只需要知道,通知Observer时,需要调用哪个统一方法就好了:

    在这里插入图片描述

    观察者模式的实现

    //观察者模式
    //内部基于发布订阅,收集观察者,状态变化后通知
    
    //被观察者
    class Subject{
        constructor(name){
            this.name = name
            this.state = '开心'
            this.observers = []
        }
        attach(o){
            this.observers.push(o)
        }
        setState(newState){
            this.state = newState
            this.observers.forEach(o=>o.update(this))
        }
    }
    //观察者
    class Observer{
        constructor(name){
            this.name = name
        }
        update(baby){
            console.log(`当前${this.name}被通知,小宝宝状态${baby.state}`)
        }
    }
    let baby = new Subject('宝宝')
    let father = new Observer('爸爸')
    let mother = new Observer('妈妈')
    
    // 被观察者接受观察
    baby.attach(father)
    baby.attach(mother)
    // 被观察者修改状态
    baby.setState('不开心')
    // 👇观察者被触发
    
    // 当前爸爸被通知,小宝宝状态不开心
    // 当前妈妈被通知,小宝宝状态不开心
    

    发布订阅模式

    比如小红最近在淘宝网上看上一双鞋子,但是这双鞋卖光了,于是小红订阅上货提醒,等有货的时候就会自动通知,与此同时,小明,小花等也喜欢这双鞋,也订阅上货提醒;等来货的时候就通过依次会通知他们;

    在上面的故事中,
    小红,小明等属于订阅者,订阅该商品;
    卖家属于发布者,当鞋子到了的时候,淘宝会依次通知小明,小红等;
    淘宝网属于第三者Broker(调度中心),将两个不相关的人物关联起来。

    发布订阅模式的优点

    1. 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象。

    2. 发布者与订阅者耦合性降低,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变

    对于第一点,我们日常工作中也经常使用到,比如我们的ajax请求,请求有成功(success)和失败(error)的回调函数,我们可以订阅ajax的success和error事件。我们并不关心对象在异步运行的状态,我们只关心success的时候或者error的时候我们要做点我们自己的事情就可以了。

    发布订阅模式的缺点

    1. 创建订阅者需要消耗一定的时间和内存。
    2. 虽然可以弱化对象之间的联系,如果过度使用的话,反而使代码不好理解及代码不好维护等等。

    发布订阅模式的实现

    1. 首先要想好谁是发布者(比如上面的卖家)。
    2. 然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者(比如上面的买家收藏了卖家的店铺,卖家通过收藏了该店铺的一个列表名单)。
    3. 最后就是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数。

    实现1:

    class Center {
      constructor() {
        this.obj = {}
      }
      on(name, fn) {//订阅
        if (!Array.isArray(this.obj[name])) {
          this.obj[name] = []
        }
        this.off(name,fn)//去重
        this.obj[name].push(fn)
      }
      off(name, fn) {//取消订阅
        let tmpObj = this.obj[name]
        for (let i = 0; i < tmpObj.length; i++) {
          if (tmpObj[i] == fn) {
            tmpObj.splice(i, 1)
            break
          }
        }
      }
      emit(parmas) {//发布
        for (let name in this.obj) {
          this.obj[name].forEach((item) => {
            item(parmas)
          })
        }
      }
    }
    let a = new Center()
    // 注意要在外部传入订阅函数,否则对象地址不同,无法匹配
    let fna = (parmas) => {
      console.log('用户1可以买' + parmas)
    }
    let fnb = (parmas) => {
      console.log('用户2可以买' + parmas)
    }
    a.on('like', fna)//订阅
    a.on('like', fnb)//订阅
    a.emit('新品')//触发
    // 用户1可以买新品
    // 用户2可以买新品
    
    a.off('like', fnb)//取消
    a.emit('新品')//触发
    // 用户1可以买新品
    
    a.on('like', fna)//二次订阅,触发一次
    a.emit('新品')//触发 
    //用户1可以买新品
    
    

    实现2

    let fs = require('fs')
    
    //第三者Broker
    let event = {
        _arr:[],
        on(fn){
            this._arr.push(fn)
        },
        emit(){
            this._arr.forEach(fn=>fn())
        }
    }
    //订阅
    event.on(function(){
        console.log(Object.keys(person))
    
        if(Object.keys(person).length===3){
            console.log(person)
        }
    })
    let person = {}
    fs.readFile('./name.txt','utf8',(err,data)=>{
        console.log(data)
        person.name = data
        //发布
        event.emit()
    })
    fs.readFile('./age.txt','utf8',(err,data)=>{
        person.age = data
        event.emit()
    })
    fs.readFile('./sex.txt','utf8',(err,data)=>{
        person.sex = data
        event.emit()
    })
    

    观察者模式VS发布订阅模式

    大概很多人都和我一样,觉得发布订阅模式里的Publisher,就是观察者模式里的Subject,而Subscriber,就是Observer。Publisher变化时,就主动去通知Subscriber。

    其实并不是。

    在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。

    互不相识?那他们之间如何交流?

    答案是,通过第三者,也就是在消息队列里面,我们常说的经纪人Broker。

    在这里插入图片描述

    发布者只需告诉Broker,我要发的消息,topic是AAA;
    订阅者只需告诉Broker,我要订阅topic是AAA的消息;

    于是,当Broker收到发布者发过来消息,并且topic是AAA时,就会把消息推送给订阅了topic是AAA的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。

    发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。

    放一张极简的图,给大家对比一下这两个模式的区别:

    在这里插入图片描述

    总结

    • 从表面上看:
      观察者模式里,只有两个角色 —— 观察者 + 被观察者
      而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker
    • 往更深层次讲:
      观察者和被观察者,是松耦合的关系
      发布者和订阅者,则完全不存在耦合
    • 从使用层面上讲:
      观察者模式,多用于单个应用内部
      发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件

    参考文章:
    https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c
    https://www.cnblogs.com/tugenhua0707/p/4687947.html
    https://zhuanlan.zhihu.com/p/51357583

    展开全文
  • 主要介绍了JavaScript设计模式之观察者模式发布订阅模式,结合实例形式详细分析了JavaScript观察者模式发布订阅模式相关概念、原理
  • 观察者模式和发布订阅模式区别

    千次阅读 2020-04-27 23:00:44
    观察者模式和发布订阅模式区别: 一、从表面上看: 观察者模式里,只有两个角色 —— 观察者 + 被观察者 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker 二...

    观察者模式和发布订阅模式区别:

    一、从表面上看:
    观察者模式里,只有两个角色 —— 观察者 + 被观察者
    在这里插入图片描述

    而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker
    在这里插入图片描述

    二、往更深层次讲:
    观察者和被观察者,是松耦合的关系
    发布者和订阅者,则完全不存在耦合

    三、从使用层面上讲:
    观察者模式,多用于单个应用内部
    发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件

    展开全文
  • 发布订阅模式和观察者模式有什么不同?

    千次阅读 多人点赞 2021-04-14 23:41:40
    发布订阅模式和观察者模式有以下不同: 1. 观察者模式中,观察者和主题都知道对方的存在;而在发布订阅模式中, 生产者与消费者不知道对方的存在,它们之间通过频道进行通信。 2. 观察者模式是同步的,当事件触发时,...

    写在前面

    本文隶属于专栏《100个问题搞定大数据理论体系》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!

    本专栏目录结构和文献引用请见100个问题搞定大数据理论体系

    解答

    在这里插入图片描述

    发布与订阅模式和观察者模式有以下不同: 
    1. 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中, 生产者与消费者不知道对方的存在,它们之间通过频道进行通信。
    2. 观察者模式是同步的,当事件触发时,主题会调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,生产者向频道发送一个消息之后, 就不需要关心消费者何时去订阅这个消息,可以立即返回。
    

    发布与订阅模式和观察者模式

    展开全文
  • 发布订阅模式与观察者模式

    万次阅读 多人点赞 2019-03-29 18:25:12
    设计模式并非是软件开发的专业术语,实际上,“模式”最早诞生于建筑学。 设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。通俗一点说,设计模式是在某种场合下对某个问题的一种...
  • 设计模式并非是软件开发的专业术语,实际上,“模式”最早诞生于建筑学。 设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。通俗一点说,设计模式是在某种场合下对某个问题的一种...
  • 以winform中parentform向childform传值为例: 降低耦合性 delegate :是一个类型...=null) { delegate1("pram")等价于 delegate1.invoke("pram") } 发布-订阅 观察者模式发布订阅的非委托实现方式 把观察者模式在优化
  • 观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。 【2】案例 案例:我办了一个补习班,学生想来我这学习,必须先...
  • 从下图中可以看出,观察者模式中观察者目标直接进行交互,而发布订阅模式中统一由调度中心进行处理,订阅者发布者互不干扰。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多...
  • 观察者模式又称为发布/订阅(Publish/Subscribe)模式 观察者设计模式涉及到两种角色:主题(Subject)观察者(Observer) (1)Subject模块 Subjec模块有3个主要操作 addObserver():注册添加观察者(申请订阅...
  • 观察者模式和发布订阅模式的概念在项目开发中很常见 ,这里记录一下自己的理解,详解一下两者的区别的并分别用代码实现来直观体现两者区别,便于日后温故知新 观察者模式 ​ 观察者模式别名也叫发布-订阅模式,但是...
  • 观察者模式也叫 发布者-订阅者模式,发布发布事件,订阅者监听事件并做出反应 在传统的前端解耦方面,观察者模式作为比较常见一种设计模式,大量使用在各种框架类库的设计当中。 核心代码: // eventProxy.js '...
  • 主要介绍了JavaScript设计模式之观察者模式发布者-订阅者模式),本文详细的讲解了JavaScript中的观察者模式,需要的朋友可以参考下
  • 观察者模式通常的叫法叫做订阅-发布模式,类似于报刊杂志的订阅,观察者被观察者就是读者邮局的关系,读者先要在邮局订阅想要的报刊,当报刊发行时,邮局会将报刊邮寄到读者家里。观察者(Observer)被观察者...
  • 改demo主要叙述java版本发布预订阅模式的主导思想(观察者模式
  • 参考了Java的部分实现,观察者模式代码如下: var subject = { observers: [], notify() { this.observers.forEach(observer =>{ observer.update() }) }, attach (observer) { this.observers....
  • EventBus系列文章: ...在EventBus的学习当中,EventBus是基于发布订阅者模式的消息处理框架。 EventBus is an open-source library for Android and Java using the publisher/subscriber pattern for loose co...
  • 观察者模式和发布/订阅模式的区别

    千次阅读 2016-03-22 22:57:26
    在处理自定义事件时,观察者模式和发布/订阅模式经常使用,起初不了解这两个模式的实现时,在网上看一些资料,很多介绍都将两种模式混淆在一起,认为他们是同一个模式、一样的实现。后来看了一些设计模式的书籍,...
  • IE 不支持,性能由浏览器优化 发布订阅模式和观察者模式 发布订阅模式和观察者模式,是两种设计模式,在 Vue 中有各自的应用场景,本质相同,但存在一定的区别。 1,发布/订阅模式 发布/订阅模式 1,订阅者 2,发布...
  • 注意:观察者模式和发布订阅模式是有区别的,区别在于观察者模式是由目标进行调度,而发布订阅模式是由独立的调度中心进行调度,可以说发布订阅模式是观察者模式进一步解耦,在实际中被大量运用的一种模式。...
  • 观察者模式 VS 发布订阅模式,面试官问我观察者模式和发布订阅模式的区别
  • 在很多地方,都会将观察者模式称为发布-订阅模式,或者订阅者模式。实际上,两者是不完全相同的,有联系也有区别。 二、观察者模式定义 观察者模式,定义了一种一对多的依赖关系,使得当一个对象发生状态变化时,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 35,094
精华内容 14,037
关键字:

发布订阅和观察者模式