专栏首页前端下午茶JS 观察者模式

JS 观察者模式

1. 介绍

观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。

使用观察者模式的好处:

  1. 支持简单的广播通信,自动通知所有已经订阅过的对象。
  2. 目标对象与观察者存在的是动态关联,增加了灵活性。
  3. 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

2. 实现一

如下例子:

  • subscribers:含有不同 type 的数组,存储有所有订阅者的数组,订阅行为将被加入到这个数组中
  • subscribe:方法为将订阅者添加到 subscribers 中对应的数组中
  • unsubscribe:方法为在 subscribers 中删除订阅者
  • publish:循环遍历 subscribers 中的每个元素,并调用他们注册时提供的方法
let publisher = {
  subscribers: {
    any: []
  },
  subscribe: function(fn, type = 'any') {
    if (typeof this.subscribers[type] === 'undefined') {
      this.subscribers[type] = []
    }
    this.subscribers[type].push(fn)
  },
  unsubscribe: function(fn, type) {
    this.visitSubscribers('unsubscribe', fn, type)
  },
  publish: function(publication, type) {
    this.visitSubscribers('publish', publication, type)
  },
  visitSubscribers: function(action, arg, type = 'any') {
    this.subscribers[type].forEach((currentValue, index, array) => {
      if (action === 'publish') {
        currentValue(arg)
      } else if (action === 'unsubscribe') {
        if (currentValue === arg) {
          this.subscribers[type].splice(index, 1)
        }
      }
    })
  }
}

let funcA = function(cl) {
  console.log('msg1' + cl)
}
let funcB = function(cl) {
  console.log('msg2' + cl)
}

publisher.subscribe(funcA)
publisher.subscribe(funcB)
publisher.unsubscribe(funcB)

publisher.publish(' in publisher')     // msg1 in publisher       

这里可以通过一个函数 makePublisher() 将一个对象复制成 publisher ,从而将其转换成一个发布者。

function makePublisher(o) {
  Object.keys(publisher).forEach((curr, index, array) => {
    if (publisher.hasOwnProperty(curr) && typeof publisher[curr] === 'function') {
      o[curr] = publisher[curr]
    }
  })
  o.subscribers={any:[]}
}

// 发行者对象
let paper = {
  daily: function() {
    this.publish('big news today')
  },
  monthly: function() {
    this.publish('interesting analysis', 'monthly')
  }
}

makePublisher(paper)

// 订阅对象
let joe = {
  drinkCoffee: function(paper) {
    console.log('Just read daily ' + paper)
  },
  sundayPreNap: function(monthly) {
    console.log('Reading this monthly ' + monthly)
  }
}

paper.subscribe(joe.drinkCoffee)
paper.subscribe(joe.sundayPreNap, 'monthly')

paper.daily()         // Just read daily big news today
paper.monthly()         // Reading this monthly interesting analysis

3. 实现二

使用ES6里的class稍微改造下:

class publisher {
    constructor() {
        this.subscribers = {
            any: []
        }
    }
    subscribe(fn, type = 'any') {
        if (typeof this.subscribers[type] === 'undefined') {
            this.subscribers[type] = []
        }
        this.subscribers[type].push(fn)
    }
    unsubscribe(fn, type) {
        this.visitSubscribers('unsubscribe', fn, type)
    }
    publish(publication, type) {
        this.visitSubscribers('publish', publication, type)
    }
    visitSubscribers(action, arg, type = 'any') {
        this.subscribers[type].forEach((currentValue, index, array) => {
            if (action === 'publish') {
                currentValue(arg)
            } else if (action === 'unsubscribe') {
                if (currentValue === arg) {
                    this.subscribers[type].splice(index, 1)
                }
            }
        })
    }
}

let publish = new publisher();

let funcA = function(cl) {
    console.log('msg1' + cl)
}
let funcB = function(cl) {
    console.log('msg2' + cl)
}

publish.subscribe(funcA)
publish.subscribe(funcB)
publish.unsubscribe(funcB)

publish.publish(' in publisher')     // msg1 in publisher

4. 实现三

以上两个方法都是《JavaScript模式》里介绍的,这里贴上个自己实现的,感觉看起来舒服点...

  • 使用IIFE的方法:
const Observer = (function() {
  const _message = {}  // 消息队列
  return {
    regist(type, fn) {          // 订阅
      _message[type]
          ? _message[type].push(fn)
          : _message[type] = [fn]
    },
    emit(type, payload) {          // 发布
      if (!_message[type]) {
        return
      }
      _message[type].forEach(event => event(payload))
    },
    remove(type, fn) {            // 退订
      if (!_message[type].includes(fn)) {return}
      const idx = _message[type].indexOf(fn)
      _message[type].splice(idx, 1)
    }
  }
})()
  • 使用ES6的class方法
class Observer {
  constructor() {
    this._message = {}
  }
  
  regist(type, fn) {          // 订阅
    this._message[type]
        ? this._message[type].push(fn)
        : this._message[type] = [fn]
  }
  
  emit(type, payload) {          // 发布
    if (!this._message[type]) {
      return
    }
    this._message[type].forEach(event => event(payload))
  }
  
  remove(type, fn) {            // 退订
    if (!this._message[type].includes(fn)) {return}
    const idx = this._message[type].indexOf(fn)
    this._message[type].splice(idx, 1)
  }
}

5. 总结

观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。

总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。


本文是系列文章,可以相互参考印证,共同进步~

  1. JS 抽象工厂模式
  2. JS 工厂模式
  3. JS 建造者模式
  4. JS 原型模式
  5. JS 单例模式
  6. JS 回调模式
  7. JS 外观模式
  8. JS 适配器模式
  9. JS 利用高阶函数实现函数缓存(备忘模式)
  10. JS 状态模式
  11. JS 桥接模式
  12. JS 观察者模式

网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出~

参考: 设计模式之观察者模式 《JavaScript模式》 《Javascript 设计模式》 - 张荣铭

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JS 桥接模式

    桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化。 其实就是函数的封装,比如要对某个DOM元素添加color和backgroundC...

    前端下午茶
  • 《图解 HTTP》 阅读摘要

    这次做了一些笔记,方便自己和其他人翻阅和复习,因为这本书是 2014 年出的初版,所以有一些不怎么常用的技术,笔记中就省略了,只记一些比较常用的 ~

    前端下午茶
  • JavaScript 设计模式系列 - 享元模式与资源池

    享元模式 (Flyweight Pattern)运用共享技术来有效地支持大量细粒度对象的复用,以减少创建的对象的数量。

    前端下午茶
  • 装饰者模式浅析

    装饰角色,持有一个Component对象的实例,并定义一个与Componnet接口一致的接口。

    孟君
  • A parameter verify tools for Egg

    最近接触EGG框架,刚接触,其中涉及到验证参数的一些运用,网上找的egg-validate 都不是很好用,最后找到了parameter插件,挺好用,推荐给大家...

    张炳
  • 【小家Spring】Spring异步处理@Async的使用以及原理、源码分析(@EnableAsync)

    在开发过程中,我们会遇到很多使用线程池的业务场景,例如异步短信通知、异步记录操作日志。大多数使用线程池的场景,就是会将一些可以进行异步操作的业务放在线程池中去完...

    YourBatman
  • 【php设计模式】适配器模式

    将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。

    码缘
  • STL源码剖析-set容器

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/d...

    bear_fish
  • Java 14 来势汹汹,这回让空指针无处遁形!!

    相信在坐的每一位 Java 程序员都遇到过空指针异常:NullPointerException(NPE),不甚其烦。

    Java技术栈
  • muduo网络库学习之Timestamp类、AtomicIntegerT 类封装中的知识点

    一、Timestamp类封装 class Timestamp : public muduo::copyable,                   pub...

    s1mba

扫码关注云+社区

领取腾讯云代金券