• js设计模式--发布订阅模式


    前言

    本系列文章主要根据《JavaScript设计模式与开发实践》整理而来,其中会加入了一些自己的思考。希望对大家有所帮助。

    概念

    发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。

    场景

    DOM事件

    
    
      document.body.addEventListener('click', function () {
        alert(2);
      }, false);
      document.body.addEventListener('click', function () {
        alert(3);
      }, false);
      document.body.addEventListener('click', function () {
        alert(4);
      }, false);
    
      document.body.click(); // 模拟用户点击
    

    优缺点

    优点:发布—订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。
    缺点:创建订阅者本身要消耗一定的时间和内存,而 且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。

    例子

    销售处订阅房源

    简单的发布订阅

    
    
    var Event = function() {
      this.list = []
    }
    
    Event.prototype.add = function(listener) {
      this.list.push(listener)
    }
    
    Event.prototype.triggle = function() {
      this.list.forEach(listener => {
        listener()
      })
    }
    
    var event = new Event()
    event.add(()=>{console.log('房源1--80平--200万')})
    event.add(()=>{console.log('房源2--200平--1000万')})
    
    event.triggle()
    

    或者

    
    
    var event = {
      list: [],
      add(listener) {
        this.list.push(listener)
      },
      triggle() {
        this.list.forEach(listener => {
          listener()
        })
      }
    }
    
    event.add(()=>{console.log('房源1--80平--200万')})
    event.add(()=>{console.log('房源2--200平--1000万')})
    
    event.triggle()
    

    但这种不能区分是发不了什么消息,比如有两群人:订阅80平房源报价和订阅200瓶房源报价的人,这里两群人都会得到通知

    改进

    
    
    var event = {
      list: {},
      add(type, listener) {
        if (!this.list[type]) {
          this.list[type] = []
        }
        this.list[type].push(listener)
      },
      triggle(type) {
        this.list[type] && this.list[type].forEach(listener => {
          listener()
        })
      }
    }
    
    event.add('80平', ()=>{console.log('房源1--80平--200万')})
    event.add('80平', ()=>{console.log('房源2--80平--300万')})
    event.add('200平', ()=>{console.log('房源2--200平--1000万')})
    
    event.triggle('80平')
    

    这里还少了一个取消订阅的功能

    增加取消订阅

    
    
    var event = {
      list: {},
      add(type, listener) {
        if (!this.list[type]) {
          this.list[type] = []
        }
        this.list[type].push(listener)
      },
      triggle(type) {
        this.list[type] && this.list[type].forEach(listener => {
          listener()
        })
      },
      remove(type, fn) {
        if (!this.list[type]) return
        var index = this.list[type].findIndex(listener => listener === fn)
        this.list[type].splice(index, 1)
      }
    }
    
    var f1 = ()=>{console.log('房源1--80平--200万')}
    var f2 = ()=>{console.log('房源2--80平--300万')}
    var f3 = ()=>{console.log('房源2--200平--1000万')}
    
    event.add('80平', f1)
    event.add('80平', f2)
    event.add('200平', f3)
    
    event.remove('80平', f2)
    event.triggle('80平') // 房源1--80平--200万
    

    上面代码结构还不是很清晰,我们再模拟销售部真实的场景

    更真实的销售部场景

    1. 销售部
    • 销售部有很多房源,如80平的,100平的等
    • 客户可以到销售部登记自己想买的房源面积,并留下姓名。到时候如果有房源,销售部就会通知客户
    • 客户由于一些原因决定不买房的时候,可以取消订阅
    1. 客户
    • 当有房源时,客户有一个接听报价的方法
    
    var Event = function () {
      this.list = {}
    }
    
    Event.prototype.add = function (area, client) {
      if (!this.list[area]) this.list[area] = []
      this.list[area].push(client)
    }
    
    Event.prototype.remove = function (area, client) {
      if (!this.list[area]) return
      var index = this.list[area].findIndex(item => item === client)
      this.list[area].splice(index, 1)
    }
    
    
    Event.prototype.triggle = function (area, price) {
      if (!this.list[area]) return
      this.list[area].forEach(client => {
        client.listen(area, price)
      })
    }
    
    var Client = function (name) {
      this.name = name
    }
    
    Client.prototype.listen = function (area, price) {
      console.log(`${this.name}收到${area}平的房源报价${price}`)
    }
    
    
    var client1 = new Client('client1')
    var client2 = new Client('client2')
    
    
    var event = new Event()
    event.add('80平', client1)
    event.add('100平', client1)
    event.add('80平', client2)
    event.add('300平', client1)
    event.remove('300平', client1)
    
    event.triggle('80平', 200) // client1收到80平平的房源报价200 client2收到80平平的房源报价200
    event.triggle('100平', 500) // client1收到100平平的房源报价500
    event.triggle('200平', 1000) //
    event.triggle('300平', 1000) //
    
    

    上面的代码虽然已经很好了,但是还是有一个缺点:订阅者接收不到订阅之前发布的消息,如下客户3也想订阅80平的房源,但他收不到任何消息

    
    
    var client3 = new Client('client3')
    event.add('80平', client3)
    

    我们希望客户3也能收到消息

    必须先订阅再发布吗

    我们增加一个cache字段来记录历史房源报价

    
    
    var Event = function () {
      this.list = {}
      this.cache = {}
    }
    
    Event.prototype.add = function (area, client) {
      if (!this.list[area]) this.list[area] = []
      this.list[area].push(client)
      this.cache[area].forEach(price => {
        client.listen(area, price)
      })
    }
    
    Event.prototype.remove = function (area, client) {
      if (!this.list[area]) return
      var index = this.list[area].findIndex(item => item === client)
      this.list[area].splice(index, 1)
    }
    
    
    Event.prototype.triggle = function (area, price) {
      if (!this.cache[area]) this.cache[area] = []
      this.cache[area].push(price)
    
      if (!this.list[area]) return
      this.list[area].forEach(client => {
        client.listen(area, price)
      })
    }
    
    var Client = function (name) {
      this.name = name
    }
    
    Client.prototype.listen = function (area, price) {
      console.log(`${this.name}收到${area}平的房源报价${price}`)
    }
    
    
    var client1 = new Client('client1')
    var client2 = new Client('client2')
    
    
    var event = new Event()
    // event.add('80平', client1)
    // event.add('100平', client1)
    // event.add('80平', client2)
    // event.add('300平', client1)
    // event.remove('300平', client1)
    
    event.triggle('80平', 200) // client1收到80平平的房源报价200 client2收到80平平的房源报价200
    event.triggle('100平', 500) // client1收到100平平的房源报价500
    event.triggle('200平', 1000) //
    event.triggle('300平', 1000) //
    
    var client3 = new Client('client3')
    event.add('80平', client3)
    event.add('100平', client3)
    

    网站登录

    假如我们正在开发一个商城网站,网站里有 header 头部、nav 导航、消息列表、购物车等模块。这几个模块的渲染有一个共同的前提条件,就是必须先用 ajax 异步请求获取用户的登录信息。

    这里留给读者自己实现

    来源:https://segmentfault.com/a/1190000017758386

  • 相关阅读:
    GZOI 2017配对统计 树状数组
    关于线段树的一些问题
    BZOJ 压力 tarjan 点双联通分量+树上差分+圆方树
    洛谷4552 差分
    洛谷5026 Lycanthropy 差分套差分
    【锁】MySQL和Oracle行锁比较
    oracle体系结构
    【加密】RSA验签及加密
    【Shiro】SpringBoot集成Shiro
    【Eureka】实现原理
  • 原文地址:https://www.cnblogs.com/datiangou/p/10224890.html
Copyright © 2020-2023  润新知