精华内容
下载资源
问答
  • 主要介绍了JS模式之简单的订阅者和发布模式,以一个完整实例形式详细分析了JS订阅者和发布模式基本实现技巧,需要的朋友可以参考下
  • 主要介绍了js实现的订阅发布模式,结合完整示例形式分析了js订阅发布模式相关实现与使用方法,需要的朋友可以参考下
  • 实例如下: //导入内置模块 let EventEmitter = require('events'); let util=require('util'); //Man继承EventEmitter util.inherits(Man,EventEmitter); //创建一个函数 function Man(){} ...man.on('失恋',findGirl...
  • 主要介绍了JavaScript设计模式之观察者模式(发布订阅模式)原理与实现方法,结合实例形式分析了JavaScript观察者模式概念、原理、使用方法及相关操作注意事项,需要的朋友可以参考下
  • js代码-发布订阅模式 JavaScript 发布-订阅模式 - 掘金 https://juejin.im/post/6844903850105634824
  • 下面小编就为大家带来一篇js 发布订阅模式的实例讲解。小编觉得挺不错的,现在就想给大家,也给大家做个参考。一起跟随小编过来看看吧
  • js发布订阅模式

    2020-03-31 20:30:23
    发布订阅模式广泛用于js中,从早期的jq事件池的机制到后面vue的eventbus都是典型的发布订阅模式。在该模式中,发布者向事件池中发送或移除事件,由事件池通知订阅者。这样大大降低了代码之间的耦合度,使得代码更...

    发布订阅模式广泛用于js中,从早期的jq事件池的机制到后面vue的eventbus都是典型的发布订阅模式。在该模式中,发布者向事件池中发送或移除事件,由事件池通知订阅者。这样大大降低了代码之间的耦合度,使得代码更利于维护。这一点也是和观察者模式最大的区别,观察者是发布者直接发送消息给订阅者,是强耦合的。

    一个简单的发布订阅模式

    let subscribe = function () {
      const isFunction = fn => typeof fn === 'function'
      class Sub {
        constructor() {
          this.eventArr = []
        }
        add(fn) {
          if (!isFunction(fn)) console.error('add must accept a function as first parameter')
          this.eventArr.includes(fn) ? null : this.eventArr.push(fn)
          return this
        }
        remove(fn) {
          if (!isFunction(fn)) console.error('remove must accept a function as first parameter')
          let index = this.eventArr.indexOf(fn)
          index > -1 ? this.eventArr.splice(index, 1) : null
          return this
        }
        fire(...args) {
          let self = this
          this.eventArr.map(function (item) {
            item.call(self, ...args)
          })
          return this
        }
      }
      return new Sub()
    }
    
    //测试
    let sub = subscribe()
    let fn1 = function () {
      console.log(1);
    }
    let fn2 = function () {
      console.log(2);
    }
    let fn3 = function (a,b) {
      console.log(a);
      console.log(b);
    }
    sub.add(fn1).add(fn2).add(fn3)
    sub.fire(3,4)           //1 2 3 4
    
    

    数组塌陷问题

    有点时候,我们在执行事件池中的函数时,可能会影响事件池。例如当我们执行以下代码时,发现它输出1 3,这是因为在执行fn1的时候,执行了remove方法,影响了当前事件池中的index,我们应期望当执行remove的时候只是将其置空,执行fire的时候再将其置空项删除

    let sub = subscribe()
    let fn1 = function () {
      console.log(1);
      sub.remove(fn1)
    }
    let fn2 = function () {
      console.log(2);
    }
    let fn3 = function () {
      console.log(3);
    }
    sub.add(fn1).add(fn2).add(fn3)
    sub.fire()             //1  3
    

    调整后代码如下

    let subscribe = function () {
      const isFunction = fn => typeof fn === 'function'
      class Sub {
        constructor() {
          this.eventArr = []
        }
        add(fn) {
          if (!isFunction(fn)) console.error('add must accept a function as first parameter')
          this.eventArr.includes(fn) ? null : this.eventArr.push(fn)
          return this
        }
        remove(fn) {
          if (!isFunction(fn)) console.error('remove must accept a function as first parameter')
          let index = this.eventArr.indexOf(fn)
          index > -1 ? this.eventArr[index] = null : null
          return this
        }
        fire(...args) {
          for (let index = 0; index < this.eventArr.length; index++) {
            if (!this.eventArr[index]) {
              this.eventArr.splice(index,1)
              index--
            } else {
              this.eventArr[index].call(this, ...args)
            }
          }
          return this
        }
      }
      return new Sub()
    }
    
    
    展开全文
  • 原生js观察者模式的的实现,主要用来管理回调函数队列,参考jquery的callbacks模块
  • JS发布订阅模式

    2020-07-21 19:37:34
    目录JS发布订阅模式一、直白的发布订阅例子二、用户只订阅自己感兴趣的消息三、发布订阅模式的通用实现四、封装成中介 - 解决代码耦合五、离线储存+命名空间 - 解决离线事件 JS发布订阅模式 一、直白的发布订阅例子 ...

    JS发布订阅模式

    一、直白的发布订阅例子

    简单的售楼处例子:

    1. 售楼处是发布者

      var salesOffices = {};
      
    2. 添加订阅者的方法,以一个函数代表一个订阅者

      /**
       * 增加订阅者
       * 
       * @param {Function} fn 发送消息给订阅者的函数
       */
      salesOffices.listen = function (fn) {
          this.clientList.push(fn)
      }
      
    3. 发布消息的方法

      salesOffices.trigger = function () {
          for (var i = 0; i < this.clientList.length; i++) {
          	fn = this.clientList[i]
              fn.apply(this, arguments)
          }
      }
      
    4. 测试

      // 添加订阅者小明
      salesOffices.listen(function (price, squareMeter) {
          console.log('price', price)
          console.log('squareMeter', squareMeter)
      })
      
      // 添加订阅者小红
      salesOffices.listen(function (price, squareMeter){
          console.log('price', price)
          console.log('squareMeter', squareMeter)
      })
      
      salesOffices.trigger(2000000, 100)
      // price 2000000
      // squareMeter 100
      // price 2000000
      // squareMeter 100
      

    二、用户只订阅自己感兴趣的消息

    1. 售楼处是发布者

      var salesOffices = {};
      
    2. 添加订阅者的方法,以一个函数代表一个订阅者

      /**
       * 增加通知订阅者
       * @param {Function} fn  发送消息给订阅者的函数
       * @param {String} event 代表某一类消息
       */
      salesOffices.listen = function (event, fn) {
      
      	// 如果还没有此类消息,给该类消息创建一个缓存列表
      	if (!this.clientList[event]) {
      		this.clientList[event] = []
      	} 
          this.clientList.push(fn)
      }
      
    3. 发布消息的方法

      /**
       * 发布消息给所有订阅此消息的人
       * 
       * @param {String} event 代表某一类消息
       * @param {*} message 发送的消息
       */
      salesOffices.trigger = function (event, message) {
      
      	// 消息标签下的订阅者
      	var subscriberFuncs = this.clientList[event]
      	
      	// 如果没有此标签或消息标签下没有订阅者
      	if (!subscriberFuncs || subscriberFuncs.length === 0) {
          	console.log('No subscribers')
          	return false
      	}
      
      	// 发布消息
          for (var i = 0, fn; fn = fns[i++]; ) {
              subscriberFuncs.apply(this, message)
          }
      }
      
    4. 测试

      // 测试:
      // 添加订阅者小明
      // 小明订阅了88平米房的信息
      salesOffices.listen('squareMeter88', function(price) {
      	console.log('price', price)
      })
      
      // 添加订阅者小红
      // 小明订阅了95平米房的信息
      salesOffices.listen('squareMeter95', function(price) {
      	console.log('price', price)
      })
      
      // 添加订阅者小李
      // 小明订阅了95平米房的信息
      salesOffices.listen('squareMeter95', function(price) {
      	console.log('price', price)
      })
      
      // 发布 88 平方米房子的价格
      salesOffices.trigger('squareMeter88', 2000000);
      // price 2000000 (通知小明)
      
      // 发布 95 平方米房子的价格
      salesOffices.trigger('squareMeter95', 2000000);
      // price 2000000 (通知小红)
      // price 2000000 (通知小李)
      
      // 发布 1000 平方米房子的价格
      salesOffices.trigger('squareMeter1000', 2000000);
      // No subscribers
      

    三、发布订阅模式的通用实现

    考虑到代码复用性,将发布—订阅的功能提取出来,放在一个单独的对象内 (Event)

    1. 定义发布者通用的基本属性和方法

      var Event = {
      	/**
      	 * 储存不同类消息下以及其下的订阅者
      	 */
          clientList: {},
      
      	/**
      	 * 增加订阅者
      	 * @param {Function} fn 发送消息给订阅者的函数
      	 */
          listen: function (event, fn) {
              if (!this.clientList[event]) {
                  this.clientList[event] = []
              }
              this.clientList[event].push(fn)
          },
      
      	/**
      	 * 发布消息给所有订阅此消息的人
      	 * @param {String} event 代表某一类消息
      	 * @param {*} message 发送的消息
      	 */
          trigger: function (event, param) {
      
              var subscriberFuncs = this.clientList[event]
      
              if (!subscriberFuncs || subscriberFuncs.length === 0) {
                  console.log('No Subscribers')
                  return false
              }
      
              for (var i = 0, fn; fn = subscriberFuncs[i++]; ) {
                  fn.apply(this, param)
              }
          },
          
          /**
      	 * 取消订阅
      	 * @param {String}   event 代表要取消的某一类消息
      	 * @param {Function} fn    代表订阅者
      	 */
      	remove: function (event, fn) {
      	    var subscriberFuncs = this.clientList[event]
      	
      	    // 如果 eventId 对应的消息没有被人订阅,则直接返回
      	    if (!subscriberFuncs) {
      	        return 
      	    }
      	
      	    // 如果没有传入具体的回调函数,表示需要取消 eventId 对应消息的所有订阅
      	    if (!fn) {
      	        subscriberFuncs.length = 0
      	        return
      	    }
      	
      	    // 取消消息 eventId 下的订阅的事件 fn
      	    for (var i = subscriberFuncs.length - 1; i >= 0; i--) {
      	        var _fn = subscriberFuncs[i]
      	        if (fn === _fn) {
      	            subscriberFuncs.splice(i, 1)
      	        }
      	    }
      	}
      };
      
    2. 让一个发布者拥有Event的属性和方法

      /**
       * 为发布者添加发布订阅模式的基本功能
       * 
       * @param {Object} obj 待添加功能的目标对象
       */
      var installEvent = function (obj) {
          for (var attr in Event) {
              obj[attr] = Event[attr]
          }
      }
      
    3. 测试

      // 测试:
      // 添加订阅者小明
      // 小明订阅了88平米房的信息
      var fnMing = function(price) {
          console.log('price', price)
      }
      salesOffices.listen('squareMeter88', fnMing)
      
      // 添加订阅者小红
      // 小明订阅了95平米房的信息
      var fnHong = function(price) {
          console.log('price', price)
      }
      salesOffices.listen('squareMeter95', fnHong)
      
      // 添加订阅者小李
      // 小明订阅了95平米房的信息
      var fnLi = function(price) {
          console.log('price', price)
      }
      salesOffices.listen('squareMeter95', fnLi)
      
      // 发布 88 平方米房子的价格
      salesOffices.trigger('squareMeter88', 2000000);
      // price 2000000 (通知小明)
      
      // 发布 95 平方米房子的价格
      salesOffices.trigger('squareMeter95', 2000000);
      // price 2000000 (通知小红)
      // price 2000000 (通知小李)
      
      
      // 小红取消订阅 squareMeter95
      salesOffices.remove('squareMeter95', fnHong)
      salesOffices.trigger('squareMeter95', 2000000);
      // price 2000000 (通知小李)
      
      // 取消所有 squareMeter88 订阅
      salesOffices.remove('squareMeter88')
      salesOffices.trigger('squareMeter88', 2000000);
      // No Subscribers
      

    四、封装成中介 - 解决代码耦合

    • Event 作为一个类似 “中介者” 的角色,把订阅者和发布者联系起来
    • Event 是一个对象,直接执行函数,内含保存订阅信息的对象clientList,返回封装好的listentriggerremove 等基本方法
    • 解决的问题是代码耦合
    var Event = (() => {
    	// 储存订阅信息的仓库
        var clientList = {}
    
    	// listen方法,负责订阅
        var listen = (event, fn) => {
            if (!clientList[event]) {
                clientList[event] = []
            }
            clientList[event].push(fn)
        }
        
    	// trigger方法,负责发布消息
    	var trigger = (event, param) => {
            var fns = clientList[event]
            if (!fns || fns.length === 0) {
                return 
            }
            for (var i = 0, fn; fn = fns[i++]; ) {
                fn.apply(this, param)
            }
        }
        
    	// remove方法,负责取消订阅
        var remove = (event, fn) => {
            var fns = clientList[event]
            if (!fns) {
                return
            }
            if (!fn) {
                fns.length === 0
                return
            }
            for (var i = 0, fn; i <= 0; i--) {
                var _fn = fns[l]
                if (_fn === fn) {
                    fns.splice(i, 1)
                }
            }
        }
    
        return { listen, trigger, remove}
    })()
    

    怎用使用?

    <html>
        <body>
            <button id="count">点击</button>
            <div id="show"></div>
        </body>
    </html>
    
    // 发布者处理发布消息事件
    var a = (function() {
        var count = 0
        var button = document.getElementById('count')
        button.onclick = function () {
            Event.trigger('add', count++)
        }
    })()
    
    // 发布者处理订阅事件
    var b = (function() {
        var div = document.getElementById('show')
        Event.listen('add', function(count) {
            div.innerHTML = count
        })
    })()
    

    五、离线储存+命名空间 - 解决离线事件

    • 需求实例:在聊天软件上,用户离线时收到朋友的消息,将这条消息保存下来,等到用户登录时,再把离线消息发给用户。即等到有对象来订阅它的时候,再重新把消息发布给订阅者。
    • 解决问题:
      • 离线消息
      • 先发布后订阅
      • 事件类型冲突

    重新定义 Event 对象

    • 仍然是立即执行函数
    • 返回一个包含不同方法的对象
    
    // Event 简略结构如下
    var Event = (() => {
    	var ... // 定义一些变量	
    	return (() => { // Event对象立即执行函数,返回一些函数
    		var ... // 一些变量
    		var _each = function(...){...} // 一些基本的工具函数,单一职责
    		var _create = function(...){...}
    		var _listen = function(...){...}
    		var _trigger = function(...){...}
    		var _remove = function(...){...}
    		// 在外部创建Event对象最终返回的对象
    		return { create: ..., one: ..., remove: ..., listen: ..., trigger: ...}
    	})()
    })()
    
    // Event的实现
    var Event = (() => {
        var _default = 'default' // 没有使用命名空间, 或者创建命名空间未传递名字时候, 使用的默认的命名空间
    
        return (() => {
            // _listen   监听函数 
            // _trigger  触发函数
            // _remove   移除函数 
            // _unshift  数组操作工具方法
            // _create   创建命名空间函数
            // _each     遍历工具函数
            // namespaceCache 命名空间 
            var _listen, _trigger, _remove, _unshift, _create, _each, namespaceCache
    
            namespaceCache = {}
            _unshift = Array.prototype.unshift
    
            /**
             * 对数组 arr 的每个元素进行操作 fn
             * 
             * @param {Array}    arr 要遍历的数组
             * @param {Function} fn  对于数组每一项进行操作的方法
             * @return 最后一个回调函数的执行结果 
             */
            _each = (arr, fn) => {
                var ret
                // 调用提供的函数, 作用域为elem, 参数为 元素elem下标、元素elem本身
                arr.forEach((elem, i) => ret = fn.call(elem, i, elem))
                // 返回最后一个回调函数的执行结果 
                return ret
            }
    
            /**
             * 添加订阅者fn
             * 没有订阅该事件则创建
             * 
             * @param {Object}   cache   给定命名空间下的缓存
             * @param {String}   eventId 事件名称标签
             * @param {Function} fn      代表订阅者,发布消息时以此fn来通知调用者
             */
            _listen = (cache, eventId, fn) => 
                cache[eventId] 
                    ? cache[eventId].push(fn) 
                    : cache[eventId] = [fn]
    
            /**
             * 发布消息
             * 
             * @param {Object} cache   给定命名空间下的缓存
             * @param {String} eventId 事件名称标签
             * @param {*}      message 发送的消息
             */
            _trigger = function (cache, eventId, message) {
                var _self = this
                var stack = cache[eventId]
    
                // 如果没有事件标签,或事件栈为空
                // 即没有此事件,或此事件没有订阅者,则直接返回
                if (!stack || stack.length === 0) {
                    return
                }
    
                // 遍历事件堆, 分别发布事件
                return _each(stack, function() {
                    return this.call(_self, message) // this 指向的是 stack[eventId] 中的函数
                })
            }
    
            /**
             * 取消订阅事件
             * 
             * @param {String}   eventId 事件名称
             * @param {Object}   cache   给定命名空间下的缓存
             * @param {Function} fn      代表订阅者,发布消息时以此fn来通知调用者
             */
            _remove = (eventId, cache, fn) => {
                // 如果有此类事件,则进行移除操作
                if (cache[eventId]) {
    
                    // 没有传递fn那么则定因为删除该事件中所有的订阅者 
                    if (!fn) {
                        cache[eventId] = []
                        return
                    }
    
                    // 删除cache中对应的订阅者
                    cache[eventId].forEach((_fn, i) => {
                        if (fn === _fn) cache[eventId].splice(i, 1)
                    })
                }
            }
    
            /**
             * 创建一个命名空间
             * 
             * @param {String} namespace 自定义的命名空间的名称
             */
            _create = (namespace) => {
                
                // 命名空间名称,没有提供则为 default
                var namespace = namespace || _default
    
                // 如果该命名空间已存在, 则直接返回
                if (namespaceCache[namespace]) {
                    return namespaceCache[namespace]
                }
    
                // 如果是新命名空间,则把此 namespace 新建到 Event 对象的 namespaceCache 中
                // 并初始化赋值
    
                // 此命名空间下的缓存
                // 形如 cache {
                //     "event1": [fn1, fn2, fn3, ...],
                //     "event2": [fn1, fn2, fn3, ...]
                // }
                var cache = {}
    
                // 此命名空间下的离线事件
                // 形如 offlineStack [fn1, fn2, fn3, ...]
                var offlineStack = []
    
                // 默认返回的对象
                // 包含 listen, one, remove, trigger 等函数
                return namespaceCache[namespace] = {
    
                    /**
                     * 添加订阅者到此命名空间下
                     * 
                     * @param {String}   event   事件标签,此命名空间下的事件标签
                     * @param {Function} fn      代表订阅者 
                     * @param {String}   last 
                     */
                    listen: (event, fn, last) => {
    
                        // 在此命名空间下的缓存 cache 里的时间标签 event 下,添加订阅者 fn 
                        _listen(cache, event, fn)
    
                        // 如果不存在离线事件,添加订阅者的函数完毕
                        if (!offlineStack) return
    
                        // 如果存在离线事件, 弹出并执行最后一条离线消息事件
                        if (last === 'last') { 
                            offlineStack.length && offlineStack.pop()()
                        } else { // 执行每条离线事件 每个离线消息都是一个function 
                            _each(offlineStack, function(){
                                this() // this 即为 offlineStack 中的函数 fn,this() 即为 fn()
                            })
                        }
    
                        // 清除离线消息缓存,添加订阅者的函数完毕
                        offlineStack = null
                    },
    
                    // 命名空间 监听一次函数 类似于jquery.one
                    one: function (eventId, fn, last) {
                        _remove(eventId, cache)
                        this.listen(eventId, fn, last); // 重新创建监听函数
                    },
    
                    remove: (eventId, fn) => _remove(eventId, cache, fn),
    
                    trigger: function () {
                        var fn, args, _self
    
                        _unshift.call(arguments, cache) // 把cache添加进arguments中
                        _self = this
                        args  = arguments
                        fn    = () => _trigger.apply(_self, args)
    
                        // 如果离线事件存在, 则直接执行
                        // 如果离线事件不存在, 则先把当前事件存到离线事件栈offlineStack里
                        return (offlineStack) ? offlineStack.push(fn) : fn()
                    }
                }
            }
    
            return {
                create: _create,
                one: function (eventId, fn, last) {
                    this.create().listen(eventId, fn, last)
                },
                remove: function (eventId, fn) {
                    this.create().remove(eventId, fn);
                },
                listen: function (eventId, fn, last) {
                    this.create().listen(eventId, fn, last);
                },
                trigger: function() { 
                    this.create().trigger.apply(this, arguments); 
                }
            }
        })()
    

    测试

    
    // 先发布后订阅 
    // 没有创建命名空间来监听和触发函数 实际上内部已经创建了一个命名空间为default
    // 1. 当先发布的时候实际上会添加进一个offlineStack这个堆里面
    Event.trigger("click", 1);
    
    // 2. 当监听的时候检测到有offlineStack则会先触发所有的离线事件
    // 而且监听一次后则会把所有的离线事件清空
    Event.listen("click", (a) => console.log(a)) // 1 
    
    // 使用命名空间 
    Event.create("namespace1").listen("click", (a) => console.log(a)) // 1
    Event.create("namespace1").trigger("click", 1) 
    Event.create("namespace2").listen("click", (a) => console.log(a)) // 2 
    Event.create("namespace2").trigger("click", 2)
    // console最终输出
    1
    1
    2
    
    展开全文
  • 上次研究观察者模式,很多文章说它也叫Subscribe/Publish(发布/订阅模式)。可在《Javascript设计模式》一书中,这两种模式还是有些区别的。书中原话如下: 1.Observer模式要求希望接收到主题通知者的观察者必须订阅...
  • 主要介绍了JavaScript事件发布/订阅模式,结合实例形式简单分析了javascript发布/订阅模式的概念、原理及简单使用方法,需要的朋友可以参考下
  • 我们先写一个简单的发布订阅: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" c...
    在这里插入图片描述

    我们先写一个简单的发布订阅:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    	<meta charset="UTF-8">
    	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    	<title>发布订阅模式</title>
    </head>
    <body>
    	<script type="text/javascript">
    		var shopObj = {} // 定义发布者 卖家对象
    		shopObj.list = [] // 缓存列表 存放订阅函数
    
    	<span class="token comment">// 添加订阅者</span>
    	shopObj<span class="token punctuation">.</span><span class="token function-variable function">listen</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">fn</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    		shopObj<span class="token punctuation">.</span>list<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span>
    	<span class="token punctuation">}</span>
    
    	<span class="token comment">// 发布消息</span>
    	shopObj<span class="token punctuation">.</span><span class="token function-variable function">trigger</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    		<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">,</span> fn<span class="token punctuation">;</span> fn <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>i<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    			<span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> arguments<span class="token punctuation">)</span>
    		<span class="token punctuation">}</span>
    	<span class="token punctuation">}</span>
    
    	<span class="token comment">// 添加第一个订阅者</span>
    	shopObj<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">color<span class="token punctuation">,</span> size</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    		console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">订阅者1:颜色是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>color<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 尺码是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>size<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
    	<span class="token punctuation">}</span><span class="token punctuation">)</span>
    
    	<span class="token comment">// 添加第二个订阅者</span>
    	shopObj<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">color<span class="token punctuation">,</span> size</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    		console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">订阅者2:颜色是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>color<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, again尺码是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>size<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
    	<span class="token punctuation">}</span><span class="token punctuation">)</span>
    
    	<span class="token comment">// 发布第一个消息 本意是要通知第1个订阅者 橘黄色 42尺码的货到了</span>
    	shopObj<span class="token punctuation">.</span><span class="token function">trigger</span><span class="token punctuation">(</span><span class="token string">'orange'</span><span class="token punctuation">,</span> <span class="token number">42</span><span class="token punctuation">)</span>
    	<span class="token comment">// 发布第二个消息 本意是要通知第2个订阅者 黑色 39尺码的货到了</span>
    	shopObj<span class="token punctuation">.</span><span class="token function">trigger</span><span class="token punctuation">(</span><span class="token string">'black'</span><span class="token punctuation">,</span> <span class="token number">39</span><span class="token punctuation">)</span>
    
    </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
    

    </body>
    </html>

    在这里插入图片描述

    可以看到,已经可以订阅消息,并且发布消息,但是有个问题,就是发布消息的时候,把消息发送给所有订阅者了,怎么才能解决这个问题呢?

    我们回忆一下,我们在使用发布订阅的时候,是不是这样使用的,要传入一个标识:
    在这里插入图片描述
    然后在触发时候,和订阅时候传入的标识对应上,是不是就能解决这个问题了

    我们这样改造: 添加订阅时候,传入一个标识符key, 如果缓存列表里不存在对应的list[key],那么就创建一个对应的list[key]初始值是一个空数组[]

    如果有的话,直接把回调函数push进入对应的list[key]
    在这里插入图片描述
    完整代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    	<meta charset="UTF-8">
    	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    	<title>发布订阅模式2-优化</title>
    </head>
    <body>
    	<script type="text/javascript">
    		var shopObj = {} // 定义发布者 卖家对象
    		shopObj.list = [] // 缓存列表 存放订阅函数
    
    	<span class="token comment">// 添加订阅者</span>
    	shopObj<span class="token punctuation">.</span><span class="token function-variable function">listen</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> fn</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    		<span class="token comment">// 没有对应key的话就创建一个数组</span>
    		<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    			<span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
    		<span class="token punctuation">}</span>
    		<span class="token comment">// 然后再把回调函数push进list[key]</span>
    		shopObj<span class="token punctuation">.</span>list<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span>
    	<span class="token punctuation">}</span>
    
    	<span class="token comment">// 发布消息</span>
    	shopObj<span class="token punctuation">.</span><span class="token function-variable function">trigger</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    		<span class="token comment">// 获取对应的key</span>
    		<span class="token keyword">var</span> key <span class="token operator">=</span> <span class="token class-name">Array</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function">shift</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>arguments<span class="token punctuation">)</span> <span class="token comment">// 这里因为arguments是一个类数组 没有shift方法 需要借用</span>
    		<span class="token keyword">var</span> fns <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>key<span class="token punctuation">]</span>
    		<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>fns <span class="token operator">||</span> fns<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    			<span class="token keyword">return</span>
    		<span class="token punctuation">}</span>
    
    		<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">,</span> fn<span class="token punctuation">;</span> fn <span class="token operator">=</span> fns<span class="token punctuation">[</span>i<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    			<span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> arguments<span class="token punctuation">)</span>
    		<span class="token punctuation">}</span>
    	<span class="token punctuation">}</span>
    
    	<span class="token comment">// 添加第一个订阅者dean</span>
    	shopObj<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token string">'dean'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">color<span class="token punctuation">,</span> size</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    		console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">订阅者dean:颜色是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>color<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 尺码是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>size<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">的鞋子到货了!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
    	<span class="token punctuation">}</span><span class="token punctuation">)</span>
    
    	<span class="token comment">// 添加第二个订阅者jing</span>
    	shopObj<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token string">'jing'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">color<span class="token punctuation">,</span> size</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    		console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">订阅者jing:颜色是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>color<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, again尺码是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>size<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">的鞋子到货了!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
    	<span class="token punctuation">}</span><span class="token punctuation">)</span>
    
    	<span class="token comment">// 发布第一个消息 通知第dean 橘黄色 42尺码的鞋子到货了</span>
    	shopObj<span class="token punctuation">.</span><span class="token function">trigger</span><span class="token punctuation">(</span><span class="token string">'dean'</span><span class="token punctuation">,</span> <span class="token string">'orange'</span><span class="token punctuation">,</span> <span class="token number">42</span><span class="token punctuation">)</span>
    
    
    	<span class="token comment">// 发布第二个消息 通知第jing 黑色 39尺码的货到了</span>
    	shopObj<span class="token punctuation">.</span><span class="token function">trigger</span><span class="token punctuation">(</span><span class="token string">'jing'</span><span class="token punctuation">,</span> <span class="token string">'black'</span><span class="token punctuation">,</span> <span class="token number">39</span><span class="token punctuation">)</span>
    
    </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
    

    </body>
    </html>

    /pre>

    在这里插入图片描述

    我们优化一下代码,再添加一个取消订阅:

    var event = {
      list: [],
      listen: function(key, fn) {
        // 没有对应key的话就创建一个数组
        if (!this.list[key]) {
          this.list[key] = []
        }
        // 然后再把回调函数push进list[key]
        shopObj.list[key].push(fn)
      },
      trigger: function() {
        // 获取对应的key
        var key = Array.prototype.shift.call(arguments)
        var fns = this.list[key]
        if (!fns || fns.length === 0) {
          return
        }
    
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> fn<span class="token punctuation">;</span> fn <span class="token operator">=</span> fns<span class="token punctuation">[</span>i<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
      <span class="token function">fn</span><span class="token punctuation">(</span><span class="token operator">...</span>arguments<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    

    },
    // 取消订阅
    remove: function(key, fn) {
    var fns = this.list[key]
    if (!fns) {
    return false
    } else {
    for (var i = fns.length - 1; i >= 0; i) {
    var _fn = fns[i]
    if (_fn == fn) {
    fns.splice(i, 1)
    }
    }
    }
    }
    }

    代码还可以用立即执行函数封装一下:

    // 代码封装成立即执行函数
    var Event = (function() {
      var list = [],
        listen,
        trigger,
        remove;
      listen = function(key, fn) {
          // 没有对应key的话就创建一个数组
          if (!this.list[key]) {
            this.list[key] = []
          }
          // 然后再把回调函数push进list[key]
          shopObj.list[key].push(fn)
        },
        trigger = function() {
          // 获取对应的key
          var key = Array.prototype.shift.call(arguments)
          var fns = this.list[key]
          if (!fns || fns.length === 0) {
            return
          }
    
      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> fn<span class="token punctuation">;</span> fn <span class="token operator">=</span> fns<span class="token punctuation">[</span>i<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token function">fn</span><span class="token punctuation">(</span><span class="token operator">...</span>arguments<span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function-variable function">remove</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> fn</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
      <span class="token keyword">var</span> fns <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>key<span class="token punctuation">]</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>fns<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">return</span> <span class="token boolean">false</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> fns<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&gt;=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
          <span class="token keyword">var</span> _fn <span class="token operator">=</span> fns<span class="token punctuation">[</span>i<span class="token punctuation">]</span>
          <span class="token keyword">if</span> <span class="token punctuation">(</span>_fn <span class="token operator">==</span> fn<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            fns<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>
          <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    

    return {
    listen,
    trigger,
    remove
    }
    })()

    我们再看一下,发布订阅模式的使用场景: 解决异步调用中的强耦合问题:

    在这里插入图片描述
    在这里插入图片描述

    这种强耦合代码,缺点是,修改一个地方,其他地方都要跟着一起修改

    使用发布订阅模式修改:
    在这里插入图片描述
    在这里插入图片描述

    我们接着在vue里面自己手写一个简易的发布订阅模块:

    Main.vue

    <template>
      <div>
        <el-input type="text" v-model="name" placeholder="Input name"></el-input>
        <el-button type="primary" @click="handlePub">Pub</el-button>
        <Son />
      </div>
    </template>
    

    <script>
    import Son from ‘./Son.vue’
    import pubsub from ‘./utils/pubsub2’
    export default {
    name: ‘Pub’,
    components: {
    Son
    },
    data () {
    return {
    name: ‘’
    }
    },
    methods: {
    handlePub () {
    pubsub.trigger(‘item’, this.name)
    }
    }
    }
    </script>

    <style>

    </style>

    Son.vue

    <template>
      <div>
        <h1>{{content}}</h1>
      </div>
    </template>
    

    <script>
    import pubsub from ‘./utils/pubsub2’
    export default {
    name: ‘Son’,
    data () {
    return {
    content: ‘’
    }
    },
    mounted () {
    let _this = this // 这里要注意this指向 或者用箭头函数
    pubsub.listen(‘item’, function(data) {
    console.log(‘data:’, data)
    console.log(‘this:’,this)
    _this.content = data
    })
    }
    }
    </script>

    <style>

    </style>

    pubsub2.js

    var Event = (function(){
      var list = [],
      listen,
      trigger,
      remove;
      listen = function (key, fn) {
        // 没有对应key的话就创建一个数组
        if (!list[key]) {
          list[key] = []
        }
        // 然后再把回调函数push进list[key]
        list[key].push(fn)
      },
      trigger = function () {
        // 获取对应的key
        var key = Array.prototype.shift.call(arguments)
        var fns = list[key]
        if (!fns || fns.length === 0) {
          return
        }
    
    <span class="token comment">// for (var i=0, fn; fn = fns[i++];) {<!-- --></span>
    <span class="token comment">//   fn(...arguments)</span>
    <span class="token comment">// }</span>
    
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">&lt;</span> fns<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
      <span class="token keyword">var</span> fn <span class="token operator">=</span> fns<span class="token punctuation">[</span>i<span class="token punctuation">]</span>
      <span class="token function">fn</span><span class="token punctuation">(</span><span class="token operator">...</span>arguments<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    

    },
    remove = function (key, fn) {
    var fns = list[key]
    if (!fns) {
    return false
    } else {
    for (var i = fns.length - 1; i >= 0; i) {
    var _fn = fns[i]
    if (_fn == fn) {
    fns.splice(i, 1)
    }
    }
    }
    }
    return {
    listen,
    trigger,
    remove
    }
    })()

    export default Event

    在这里插入图片描述
    效果:
    在这里插入图片描述

    转载:https://blog.csdn.net/dyw3390199/article/details/118651237?utm_medium=distribute.pc_feed_v2.none-task-blog-personrec_tag-6.pc_personrecdepth_1-utm_source=distribute.pc_feed_v2.none-task-blog-personrec_tag-6.pc_personrec
    展开全文
  • 主要介绍了JavaScript设计模式之观察者模式与发布订阅模式,结合实例形式详细分析了JavaScript观察者模式与发布订阅模式相关概念、原理
  • JS简单实现发布订阅模式

    千次阅读 2021-01-30 17:36:52
    发布订阅模式发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。 那他们之间如何交流? 由一个调度中心来做中间人,发布者更新(publish)主题,由主题(调度中心)来进行通知...

    在这里插入图片描述

    发布订阅模式

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

    那他们之间如何交流?

    由一个调度中心来做中间人,发布者更新(publish)主题,由主题(调度中心)来进行通知(noticy)订阅者更新(update)。
    在这里插入图片描述

    简单的代码实现

    参考网上class面向对象实现,相较于很多js event的实现便于理解

    // 主题
    class Dep {
      constructor(callback) {
        this.subs = []; // 主题的订阅者
        this.callback = callback;
      }
    
      // 添加订阅者
      addSub(sub) {
        this.subs.push(sub);
        return this;
      }
    
      // 主题更新通知---调用订阅者update,通知所有订阅者
      notify() {
        this.subs.forEach(item => item.update(this.callback));
      }
    }
    
    // 订阅者
    class Sub {
      constructor(val) {
        this.val = val;
      }
    
      update(callback) {
        this.val = callback(this.val); // 执行订阅主题的函数
        console.log('更新之后:', this.val);
      }
    }
    
    // 发布者
    class Pub {
      constructor() {
        this.deps = []; // 发布的主题列表
      }
    
      // 添加主题
      addDep(dep) {
        this.deps.push(dep);
      }
    
      // 移除主题
      removeDep(dep) {
        let index = this.deps.indexOf(dep);
        if (index !== -1) {
          this.deps.splice(index, 1);
          return true;
        } else {
          return false;
        }
      }
    
      // 更新主题
      publish(dep) {
        this.deps.forEach(item => item == dep && item.notify());
      }
    }
    
    // 新建主题,给主题中加订阅者
    let dep1 = new Dep(item => item * item);
    dep1.addSub(new Sub(1)).addSub(new Sub(2)).addSub(new Sub(3));
    
    // 新建发布者
    let pub = new Pub();
    // 添加主题
    pub.addDep(dep1);
    
    // 发布者发布,通知这个主题的所有订阅者更新
    pub.publish(dep1);
    
    // 输出结果
    // 更新之后结果:1
    // 更新之后结果:4
    // 更新之后结果:9
    
    展开全文
  • JavaScript 发布订阅模式实现

    千次阅读 2018-09-15 00:13:46
    1.emitter.on(name,fn) //订阅name事件,监听函数为fn,可多次订阅 2.emitter.once(name,fn) //功能与on类似,但监听函数为一次性的,触发后自动移除 3.emitter.emit(name,data1,data2,...,datan) //发布name事件...
  • 发布订阅模式 订阅: 由多个对象(用户)发起 发布: 在js中,可以这样做: 有一个对象,使用使用数组,把所有的对应函数,都先装起来 当需要的时候,把所有的函数都调用 <!DOCTYPE html> <...
  • javascript 发布订阅模式什么是发布订阅模式javascript 中的发布订阅模式 什么是发布订阅模式 发布订阅模式是一种【一】对【多】的依赖关系。通过【多】订阅某个主题对象,而当主题对象变更时,则会发布消息通知到...
  • js实现发布订阅模式

    千次阅读 2020-09-06 20:50:08
    定义 发布订阅是一种消息范式,消息的发送者(发布者)不会将消息直接发送给特定的接收者(订阅者),而是将发布的消息发给消息代理,由消息代理对消息进行过滤,消息代理通常执行存储转发的功能将消息从发布者发送...
  • js:发布-订阅模式

    2017-11-29 16:12:19
    发布-订阅模式
  • 观察者模式: 观察者观察被观察者,当被观察者发生...发布订阅模式: 订阅者订阅想要观察的事件,当发布者发布事件时,会去查找对应事件的订阅事件并触发。 // 事件大厅 class Event { constructor() { this.list = {}
  • 简单来说,发布订阅模式就是一种一对多的依赖关系。多个订阅者(一般是注册的函数)同时监听同一个数据对象,当这个数据对象发生变化的时候会执行一个发布事件,通过这个发布事件会通知到所有的订阅者,使它们能够...
  • 主要介绍了JavaScript实现与使用发布/订阅模式,较为详细的分析了发布/订阅模式的概念、原理并结合实例形式分析了javascript实现与使用发布/订阅模式的相关操作技巧,需要的朋友可以参考下
  • 来自迅雷前端的一道笔试题 ...3.emitter.emit(name,data1,data2,…,datan) //发布name事件,所有订阅该事件的监听函数被触发,data1,…,datan作为参数传给监听函数,若有多个函数,按照顺序执行 4.emitter.r
  • 观察者模式和发布订阅模式的概念在项目开发中很常见 ,这里记录一下自己的理解,详解一下两者的区别的并分别用代码实现来直观体现两者区别,便于日后温故知新 观察者模式 ​ 观察者模式别名也叫发布-订阅模式,但是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,995
精华内容 10,798
关键字:

js发布订阅模式