1.发布-订阅模式

一、概念

1.定义

发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到状态改变的通知。

订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

二、实现

1.实现思路

  • 创建一个对象
  • 在该对象上创建一个缓存列表(调度中心Event Channel)
  • on方法用来把函数添加到缓存列表中(订阅者注册事件到调度中心)
  • emit方法取到argument里第一个当作event,根据event值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
  • off方法可以根据event值取消订阅(取消订阅)
  • once方法只监听一次,调用完毕后删除缓存函数(订阅一次)

2.简单demo

class EventEmitter {
  constructor() {
    // 缓存列表
    this.listener = {};
  }

  // 订阅
  on(eventName, fn) {
    // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
    // 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
    if (!this.listener[eventName]) {
      this.listener[eventName] = [];
    }
    this.listener[eventName].push(fn);
  }

  // 取消订阅
  off(eventName, fn) {
    let callbacks = this.listener[eventName];
    // 缓存列表中没有对应的fn,返回false
    if (!callbacks) {
      return false;
    }
    if (!fn) {
      // 如果未传入fn,则将缓存列表中对应的fn都清空
      callbacks && (callbacks.length = 0);
    } else {
      let cb;
      // 遍历所对应的fn,判断和那个fn相同,相同则删除
      for (let i = 0, cbLen = callbacks.length; i < cbLen; i++) {
        cb = callbacks[i];
        if (cb == fn || cb.fn == fn) {
          callbacks.splice(i, 1);
          break;
        }
      }
    }
  }

  // 监听一次
  once(eventName, fn) {
    // 先绑定,运行时删除对应的值
    let on = () => {
      this.off(eventName, on);
      fn.apply(this, arguments);
    };

    on.fn = fn;
    this.on(eventName, on);
  }

  // 发布
  emit(eventName, data) {
    const callbacks = this.listener[eventName];
    if (callbacks) {
      callbacks.forEach((c) => {
        c(data);
      });
    }
  }
}

let a = new EventEmitter();
function aa(x) {
  console.log(x);
}
a.on("kak", aa);
a.on("kak", (data) => {
  console.log("1", data);
});

a.emit("kak", "hahahah");
a.off("kak", aa);
a.emit("kak", "hahahah");

3.vue中的event Bus

function eventsMixin(Vue) {
  var hookRE = /^hook:/;
  Vue.prototype.$on = function (event, fn) {
    var this$1 = this;

    var vm = this;
    // event 为数组时,循环执行 $on
    if (Array.isArray(event)) {
      for (var i = 0, l = event.length; i < l; i++) {
        this$1.$on(event[i], fn);
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn);
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true;
      }
    }
    return vm;
  };

  Vue.prototype.$once = function (event, fn) {
    var vm = this;
    // 先绑定,后删除
    function on() {
      vm.$off(event, on);
      fn.apply(vm, arguments);
    }
    on.fn = fn;
    vm.$on(event, on);
    return vm;
  };

  Vue.prototype.$off = function (event, fn) {
    var this$1 = this;

    var vm = this;
    // all,若没有传参数,清空所有订阅
    if (!arguments.length) {
      vm._events = Object.create(null);
      return vm;
    }
    // array of events,events 为数组时,循环执行 $off
    if (Array.isArray(event)) {
      for (var i = 0, l = event.length; i < l; i++) {
        this$1.$off(event[i], fn);
      }
      return vm;
    }
    // specific event
    var cbs = vm._events[event];
    if (!cbs) {
      // 没有 cbs 直接 return this
      return vm;
    }
    if (!fn) {
      // 若没有 handler,清空 event 对应的缓存列表
      vm._events[event] = null;
      return vm;
    }
    if (fn) {
      // specific handler,删除相应的 handler
      var cb;
      var i$1 = cbs.length;
      while (i$1--) {
        cb = cbs[i$1];
        if (cb === fn || cb.fn === fn) {
          cbs.splice(i$1, 1);
          break;
        }
      }
    }
    return vm;
  };

  Vue.prototype.$emit = function (event) {
    var vm = this;
    {
      // 传入的 event 区分大小写,若不一致,有提示
      var lowerCaseEvent = event.toLowerCase();
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          'Event "' +
            lowerCaseEvent +
            '" is emitted in component ' +
            formatComponentName(vm) +
            ' but the handler is registered for "' +
            event +
            '". ' +
            "Note that HTML attributes are case-insensitive and you cannot use " +
            "v-on to listen to camelCase events when using in-DOM templates. " +
            'You should probably use "' +
            hyphenate(event) +
            '" instead of "' +
            event +
            '".'
        );
      }
    }
    var cbs = vm._events[event];
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs;
      // 只取回调函数,不取 event
      var args = toArray(arguments, 1);
      for (var i = 0, l = cbs.length; i < l; i++) {
        try {
          cbs[i].apply(vm, args);
        } catch (e) {
          handleError(e, vm, 'event handler for "' + event + '"');
        }
      }
    }
    return vm;
  };
}

/***
 * Convert an Array-like object to a real Array.
 */
function toArray(list, start) {
  start = start || 0;
  var i = list.length - start;
  var ret = new Array(i);
  while (i--) {
    ret[i] = list[i + start];
  }
  return ret;
}

三、 总结

1、优点

  • 对象之间解耦
  • 异步编程中,可以更松耦合的代码编写

2、缺点

  • 创建订阅者本身要消耗一定的时间和内存
  • 虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • 设计模式汇总: 发布订阅模式 1. 定义 发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变...
    江平路阅读 16,819评论 0 6
  • 详解 ps:什么是设计模式是设计思路,一定程度提高效率在基础的23中设计模式中进行使用和优化和改良。 1.发布-订...
    JX灬君阅读 557评论 0 1
  • 定义 发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改...
    TmsGirafee阅读 686评论 0 0
  • 什么是发布-订阅模式? 1、说一下定义噻发布-订阅模式其实是一种一对多的依赖关系,当一个对象的状态发生改变时,所有...
    被风吹起来的小刘海儿阅读 607评论 0 0
  • 发布订阅模式 发布/订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依...
    自度君阅读 1,970评论 0 2