一、定义

  又叫观察者模式,他定义对象间的依照那个一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将的到通知。在javascript中,我们一般用时间模型来替代传统的发布-订阅模式

二、举例

  js中对dom元素绑定事件监听,就是简单的发布-订阅。另外在很多的框架和插件中都会存在使用这种方式来,比如vue的watch

三、结构

  发布-订阅模式可以分为两种形式,一种是发布者和订阅者直接进行通信,其结构如下:

  另一种是通过中介进行通信,发布者和订阅者互不相知,其结构如下:

四、实现

1.发布者和订阅者直接进行通信

  这种模式的核心在于,要在发布者中维护保存一个订阅者的回调函数的数组。

  典型的例子就是绑定dom元素,但是js未将对应的发布者暴露,这是浏览器实现的,因此我们使用的都是直接进行订阅。我们可以自定义一个事件,代码如下:

  1. // 发布者
  2. var publisher = {
  3. clientList: {},
  4. listen: function(key, fn){
  5. if(!this.clientList[key]) {
  6. this.clientList[key] = [];
  7. }
  8. this.clientList[key].push(fn);
  9. },
  10. trigger: function(){
  11. var key = Array.prototype.shift.call(arguments), // 获取发布的事件名称
  12. fns = this.clientList[key]; // 获取该事件下所有的回调函数列表
  13. if(!fns || fns.length === 0){
  14. return false;
  15. }
  16. for(var i = 0, l = fns.length; i < l; i++){
  17. fns[i].apply(this, arguments);
  18. }
  19. },
  20. run: function(){
  21. // 发布者根据实际情况在合适时机发布事件
  22. this.trigger("start_load", "开始加载");
  23. console.log("start load");
  24. this.trigger("loading", "正在加载");
  25. console.log("loading...");
  26. this.trigger("finish_load", "加载完成");
  27. console.log("finish load");
  28. }
  29. };
  30.  
  31. // 订阅者
  32. var subscriber = {
  33. init: function(){
  34. // 订阅
  35. publisher.listen("finish_load", function(rst){
  36. console.log("我是订阅者,我订阅了发布者的加载完成事件,现在我收到了发布者的信息:" + rst);
  37. });
  38. }
  39. };
  40.  
  41. subscriber.init();
  42.  
  43. publisher.run();

  这种的模式很简单,但是他的缺点在于如果有多个发布者,那么就需要让每个发布者维护listen、trigger函数和一个事件回调函数缓存列表,比如我们可以会对js文件的加载过程进行订阅,也可能会对dom的构建过程进行订阅等等,显然每个发布者分别创建一个对象是耗费内存也是不优雅的。另外这种模式存在着发布者和订阅这的耦合性,往往在开发过程中,我们可能根本没有必要让发布者和订阅者进行通信,各自做好自己的事情就好了。因此这种方式很少会用到。

2.通过中介进行通信

  针对上面的方式的缺点,就有了这种方式,这样的模式是一种全局的发布-订阅模式。其核心是创建一个中介,也就是一个全局的Event对象,让他来帮助发布者和订阅者沟通。代码如下:

  1. // 事件对象,作为中介
  2. var Event = {
  3. clientList: {},
  4. listen: function(key, fn){
  5. if(!this.clientList[key]) {
  6. this.clientList[key] = [];
  7. }
  8. this.clientList[key].push(fn);
  9. },
  10. trigger: function(){
  11. var key = Array.prototype.shift.call(arguments), // 获取发布的事件名称
  12. fns = this.clientList[key]; // 获取该事件下所有的回调函数列表
  13. if(!fns || fns.length === 0){
  14. return false;
  15. }
  16. for(var i = 0, l = fns.length; i < l; i++){
  17. fns[i].apply(this, arguments);
  18. }
  19. }
  20. };
  21. // 发布者
  22. var publisher = {
  23. run: function(){
  24. Event.trigger("start_load", "开始加载");
  25. console.log("start load");
  26. Event.trigger("loading", "正在加载");
  27. console.log("loading...");
  28. Event.trigger("finish_load", "加载完成");
  29. console.log("finish load");
  30. }
  31. };
  32.  
  33. // 订阅者
  34. var subscriber = {
  35. init: function(){
  36. // 订阅
  37. Event.listen("finish_load", function(rst){
  38. console.log("我是订阅者,我订阅了发布者的加载完成事件,现在我收到了发布者的信息:" + rst);
  39. });
  40. }
  41. };
  42. subscriber.init();
  43. publisher.run();

  我们看绘制二维地图的leaflet框架中,对于发布-订阅模式的实现:

  1. export var Events = {
  2. // 添加监听事件,types:{mouseclick: fn, dbclick: fn} 或者:"mouseclick dbclick"
  3. on: function (types, fn, context) {...},
  4. // 移除事件,若未设置任何参数,则删除该对象所有的事件。若fn未设置,则删除对象中所有的type事件
  5. off: function (types, fn, context) {...},
  6.  
  7. // 内部注册监听事件
  8. _on: function (type, fn, context) {
  9. this._events = this._events || {};
  10. var typeListeners = this._events[type]; // 获取对象中其他注册的该事件的回调函数
  11. // 若对象中未曾设置过相同事件名称,则保存其回调函数
  12. if (!typeListeners) {
  13. typeListeners = [];
  14. this._events[type] = typeListeners;
  15. }
  16.  
  17. var newListener = {fn: fn, ctx: context};
  18. ...
  19. typeListeners.push(newListener);
  20. },
  21.  
  22. // 移除事件, 若未设置fn则删除所有的type事件。,否则删除对应的事件
  23. _off: function (type, fn, context) {...},
  24.  
  25. // 触发对象中的所有type事件,若设置了propagate则触发父对象的type事件
  26. fire: function (type, data, propagate) {
  27. if (!this.listens(type, propagate)) { return this; } // 检查是否注册了type事件
  28. // 构建回调函数中参数事件对象
  29. var event = Util.extend({}, data, {
  30. type: type,
  31. target: this,
  32. sourceTarget: data && data.sourceTarget || this
  33. });
  34. if (this._events) {
  35. var listeners = this._events[type];
  36.  
  37. // _firingCount用于防止触发事件未执行完成同时删除该事件。
  38. // _firingCound表示正在执行的回调函数的个数,当为0时表示没有正在执行的事件。可以直接删除,否则需要将_events进行复制,防止删除掉需要回调的对象
  39. if (listeners) {
  40. this._firingCount = (this._firingCount + 1) || 1;
  41. // 执行对象注册的所有该事件的回调函数
  42. for (var i = 0, len = listeners.length; i < len; i++) {
  43. var l = listeners[i];
  44. l.fn.call(l.ctx || this, event);
  45. }
  46. this._firingCount--;
  47. }
  48. }
  49. return this;
  50. },
  51.  
  52. listens: function (type) {
  53. var listeners = this._events && this._events[type];
  54. return !!(listeners && listeners.length);
  55. },
  56. };
  57. export var Evented = Class.extend(Events);

  使用Event基础类用来实现发布-订阅,而这个基础类类似于一个接口,他需要依附于一个实际的对象来构造该对象的事件系统,比如框架中的图层需要有各种鼠标、键盘触摸事件,因此为了模仿类似dom一样的触发方式,图层类就要继承这个Event类:

  因此在订阅的时候,就可以直接:

  1. layer.on("click",function(){});

五、总结

发布-订阅模式的关键在于用一个事件对象对其进行实现,其中需要有以下几点:

  1.缓存对象:用于保存订阅者监听的回调函数,键为事件名称,值为该事件名下所有的回调函数,其结构形如:

  1. var cache = {
  2. "click": [fn1,fn2,fn3...],
  3. "dbclick": [fn4,fn5,fn6...]
  4. ...
  5. }

  2.listen/on函数:监听函数,为订阅者使用,通常包含两个参数:事件名称和回调函数,内部会将这两个参数保存到缓存对象中

  3.trigger/fire函数:发布函数,为发布者使用,通常包含一个参数:事件名称。内部通过事件名称到缓存对象中查找对应的回调函数数组,并依次执行

  4.remoe/off函数:接触监听函数,为订阅者使用,通常包含一个或两个参数:事件名称或注册监听时的回调函数。若为一个参数事件名称,则会到缓存函数中查找到对应的注册的回调函数的数组,并将其清空。若有第二个参数回调函数,会在缓存对象中找到事件名称对应的回调函数数组,查找是否存在参数中的回调函数,有的话,则只删除这一个回调函数

javascript中的设计模式之发布-订阅模式的更多相关文章

  1. JavaScript设计模式(发布订阅模式)

    发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知.在JavaScript开发中,我们一般用事件模型来替代传统的发布—订阅模式 ...

  2. [转] JavaScript设计模式之发布-订阅模式(观察者模式)-Part1

    <JavaScript设计模式与开发实践>读书笔记. 发布-订阅模式又叫观察者模式,它定义了对象之间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖它的对象都将得到通知. 例如 ...

  3. JavaScript设计模式_05_发布订阅模式

    发布-订阅模式,定义了对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都将得到通知.发布-订阅模式是使用比较广泛的一种模式,尤其是在异步编程中. /* * pre:发布-订阅 ...

  4. Javascript设计模式之发布-订阅模式

    简介 发布-订阅模式又叫做观察者模式,他定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知. 回忆曾经 作为一名前端开发人员,给DOM节点绑定事件可是再频繁不过 ...

  5. javaScript设计模式:发布订阅模式

    发布订阅模式的思想是在观察者模式的基础上演变而来,在观察者模式中客户端监听到对象某个行为就触发对应任务程序.而在发布订阅模式中依然基于这个核心思想,所以有时候也会将两者认为是同一种设计模式.它们的不同 ...

  6. 第五章 --- 关于Javascript 设计模式 之 发布-订阅模式

    先来个最简单的 发布订阅模式 document.body.addEventListener('click',function(){ alert(123); }); document.body.clic ...

  7. 浅谈js设计模式之发布 — 订阅模式

    发布 — 订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知.在 JavaScript开发中,我们一般用事件模型来替代传统的发布 — ...

  8. js设计模式之发布订阅模式

    1. 定义 发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知. 订阅者(Subscriber)把自己想订阅的事件注册(Subscri ...

  9. js设计模式之发布/订阅模式模式

    一.前言 发布订阅模式,基于一个主题/事件通道,希望接收通知的对象(称为subscriber)通过自定义事件订阅主题,被激活事件的对象(称为publisher)通过发布主题事件的方式被通知. 就和用户 ...

随机推荐

  1. 慕课网--mysql开发技巧一 学习笔记

    现在存在下面的两张表,表的结构如下所示 师徒四人表结构:id,user_name,over数据:id user_name over1 唐僧 旃檀功德佛2 猪八戒 净坛使者3 孙悟空 斗战胜佛4 沙僧 ...

  2. mysql主从搭建操作

    1.搭建说明准备工作:主从库已安装mysql软件以及xtracbackup备份工具.具体操作可参见mysql rpm安装文档. 介质 版本操作系统 Red Hat Enterprise Linux S ...

  3. YOLO-V3实战(darknet)

    一. 准备工作 1)实验环境: darknet 是由 C 和 CUDA 开发的,不需要配置其他深度学习的框架(如,tensorflow.caffe 等),支持 CPU 和 GPU 运算,而且安装过程非 ...

  4. Jmeter系列(37)- 详解 ForEach控制器

    如果你想从头学习Jmeter,可以看看这个系列的文章哦 https://www.cnblogs.com/poloyy/category/1746599.html 前言 ForEach 控制器一般和用户 ...

  5. Tomcat的启停脚本源码解析

    Tomcat是一款我们平时开发过程中最常用到的Servlet容器.本系列博客会记录Tomcat的整体架构.主要组件.IO线程模型.请求在Tomcat内部的流转过程以及一些Tomcat调优的相关知识. ...

  6. Flv.js文档使用随记

    关键字:Flv.js | Flv js | Flv-js | HTML5 FLV Player | 0x001: 前言以下涉及到 flv.js 所有内容均是V1.5.0版本内的,如方法.属性.常量.监 ...

  7. thinkphp将对象变成一维数组?

    thinkphp使用select查询出的数据为二维数组,如果想将二维数组转一维,可以使用: $x为二维数组对象 php版本要大于5.5.0 $userid_array = array_column($ ...

  8. 【状压DP】SCOI2005-洛谷P1896-互不侵犯 (状压例题)

    [状压DP]SCOI2005-洛谷P1896-互不侵犯 (状压例题) 标签(空格分隔): 状压DP 好久没写博客了,真的爽(误) 题目: 在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方 ...

  9. day55 js进阶

    目录 引子 一.BOM操作 1 window对象 2 window子对象 3 history对象 4 location对象(掌握) 5 弹出框 6 计时器相关 二.DOM操作 1 查找标签 1.1 直 ...

  10. FileBeat yml配置文件 -常用参数详解

    #filebeat 5.2.2 #prospector(input)段配置 filebeat.prospectors: #每一个prospectors,起始于一个破折号"-" - ...