撸一个JS的事宜治理模块

关于事宜

在我们运用javascript开辟时,我们会经常用到许多事宜,如点击、键盘、鼠标等等,这些物理性的事宜。而我们本日所说的我称之为事宜的,是另一种情势的事宜,定阅—宣布,又叫做观察者形式,他定义了一对多的依靠关联,当一个对象状况发作转变时,一切依靠于它的对象都邑收到关照,而在javascript中,平常习惯性的用事宜模子来替换宣布—定阅形式。

枚举一个生涯中的例子来协助人人邃晓这一种形式。酷热的炎天,妈妈烧好了饭盛上桌,冒着热气,这时刻妈妈喊小明用饭(小明在旁边的屋子里饿着肚子大吉大利晚上吃鸡…),小明出来一看,跟妈妈说,等一会 ‘饭凉了’ 再叫我,太烫了…十分钟后…妈妈喊你 ‘饭凉了’,快来用饭,而这时刻小明听到了妈妈的喊话说 ‘饭凉了’,便疾速的出来吃完了。这个例子,就是以上引见的定阅—宣布形式。例子中的小明就是定阅者(定阅的是 ‘饭凉了’),而妈妈则是宣布者(将信号 ‘饭凉了’ 宣布出去)。

运用定阅—宣布形式的有着不言而喻的长处:定阅者不必时时刻刻都讯问宣布者饭是不是凉了,在适宜的事宜点,宣布者会关照这些定阅者,通知他们饭凉了,他们能够过来吃了。如许就不必把小明和妈妈强耦合在一同,当小明的弟弟mm都想在饭凉了在用饭,只需通知妈妈一声。就像每一个看官肯建都打仗过的一种定阅—宣布:DOM事宜的绑定

document.body.addEventListener('click', function (e) {
     console.log('我实行了...')
}, false)

回归正题:

*event-mange 经由历程定阅-宣布形式完成的*

一步一步的完成

event-mange 模块的重要要领

  • on:定阅者,增加事宜
  • emit:宣布者, 动身事宜
  • once: 定阅者,增加只能监听一次以后就失效的事宜
  • removeListener:删除单个定阅(事宜)
  • removeAllListener: 删除单个事宜范例的定阅或删除悉数定阅
  • getListenerCount:取得定阅者的数目

event-mange 模块的重要属性

  • MaxEventListNum: 设置单个事宜最多定阅者数目(默以为10)

基础骨架

起首,我们愿望经由历程 event.on , event.emit 来定阅和宣布,经由历程组织函数来建立一个event实例,而on,emit分别为这个实例的两个要领, 一样的,以上列出的一切重要要领,都是event的对象的原型要领。

function events () {};

// 枚举去我们想要完成的event对象的要领

event.prototype.on = function () {};

event.prototype.emit = function () {};

event.prototype.once = function () {};

event.prototype.removeListener = function () {};

event.prototype.removeAllListener = function () {};

event.prototype.getListenerCount = function () {};

好像丢了什么,没错,是event对象我们上面列出来的MaxEventListNum属性,我们给他补上

function event () {
    //由于MaxEventListNum属性是能够让开辟者设置的
    //所以在没有set的时刻,我们将其设置为 undefind
    this.MaxEventListNum = this.MaxEventListNum || undefined;

    //假如没有设置set,我们不能让监听数目无限大
    //如许有可能会形成内存溢出
    //所以我们将默许数目设置为10(固然,设置成别的数目也是能够的)
    this.defaultMaxEventListNum = 10;
}

到这里,基础上我们想完成的时间管理模块属性和要领的初态也就差不多了,也就是说,骨架出来了,我们就须要填饱他的代码逻辑,让他变的有血有肉(看似像个性命…)

值得思索的是,骨架我们构建完了,我们要做的是一个定阅–宣布形式,我们应当怎样去记着浩瀚的定阅事宜呢? 起首,关于一个定阅,我们须要有一个定阅的范例,也就是topic,针对此topic我们要把一切的定阅此topic的事宜都放在一同,对,能够挑选Array,开端的组织

event_list: {
    topic1: [fn1, fn2, fn3 ...]
    ...
}

那末接下来我们将寄存我们事宜的event_list放入代码中完美,作为event的属性

function event () {
    // 这里我们做一个简朴的推断,以避免一些不测的毛病涌现
    if(!this.event_list) {
        this.event_list = {};
    }

    this.MaxEventListNum = this.MaxEventListNum || undefined;
    this.defaultMaxEventListNum = 10;
}

on 要领完成

event.prototype.on = function () {};

经由历程剖析得出on要领起首应当吸收一个定阅的topic,其次是一个当此topic响应后触发的callback要领

event.prototype.on = function (eventName, content) {};

eventName作为事宜范例,将其作为event_list的一个属性,一切的事宜范例为eventName的监听都push到eventName这个数组内里。

event.prototype.on = function (eventName, content) {
    ...
    var _event, ctx;
    _event = this.event_list;
    // 再次推断event_list是不是存在,不存在则从新赋值
    if (!_event) {
      _event = this.event_list = {};
    } else {
      // 猎取当前eventName的监听
      ctx = this.event_list[eventName];
    }
    // 推断是不是有此监听范例
    // 假如不存在,则示意此事宜第一次被监听
    // 将回调函数 content 直接赋值
    if (!ctx) {
      ctx = this.event_list[eventName] = content;
      // 转变定阅者数目
      ctx.ListenerCount = 1;
    } else if (isFunction(ctx)) {
      // 推断此属性是不是为函数(是函数则示意已经有且只要一个定阅者)
      // 将此eventName范例由函数转变成数组
      ctx = this.event_list[eventName] = [ctx, content];
      // 此时定阅者数目变成数组长度
      ctx.ListenerCount = ctx.length;
    } else if (isArray(ctx)) {
      // 推断是不是为数组,假如是数组则直接push
      ctx.push(content);
      ctx.ListenerCount = ctx.length;
    }
    ...
};

once 要领完成

event.prototype.once = function () {};

once要领对已定阅事宜只实行一次,需实行完后马上在event_list中响应的定阅范例属性中删除该定阅的回调函数,其存储历程与on要领险些一致,一样须要一个定阅范例的topic,以及一个响应事宜的回调 content

event.prototype.once = function (eventName, content) {};

在实行完本次事宜回调后马上作废注册此定阅,而假如此时统一范例的事宜注册了多个监听回调,我们没法准确的删除当前once要领所注册的监听回调,所以一般我们采纳的遍历事宜监听行列,找到响应的监听回调然后将其删除是行不通的。还好,巨大的javascript言语为我们供应了一个壮大的闭包特征,经由历程闭包的体式格局来装潢content,包装成一个全新的函数。

events.prototype.once = function (event, content) {
    ...
    // once和on的存储事宜回调机制雷同
    // dealOnce 函数 包装函数
    this.on(event, dealOnce(this, event, content));
    ...
  }

// 包装函数
function dealOnce(target, type, content) {
    var flag = false;
    // 经由历程闭包特征(会将函数外部援用保存在作用域中)
    function packageFun() {
      // 当此监听回调被挪用时,会先删除此回调要领
      this.removeListener(type, packageFun);
      if (!flag) {
        flag = true;
        // 由于闭包,所以原监听回调还会保存,所以还会实行
        content.apply(target, arguments);
      }
      packageFun.content = content;
    }
    return packageFun;
  }

once的完成实在将我们本身通报的回调函数做了二次封装,再绑定上封装后的函数,封装的函数起首实行了removeListener()移除了回调函数与事宜的绑定,然后才实行的回调函数

emit 要领完成

event.prototype.emit = function () {};

emit要领用来宣布事宜,驱动实行响应的事宜监听行列中的监听回调,故我们须要一个事宜type的topic

event.prototype.emit = function (eventName[,message][,message1][,...]) {};

固然,宣布事宜是,也能够像该事宜监听者通报参数,数目不限,则会顺次通报给一切的监听回调

event.prototype.emit = function (eventName[,message]) {
    var _event, ctx;
    //除第一个参数eventNmae外,其他参数保存在一个数组里
    var args = Array.prototype.slice.call(arguments, 1);
    _event = this.event_list;
    // 检测存储事宜行列是不是存在
    if (_event) {
      // 假如存在,得到此监听范例
      ctx = this.event_list[eventName];
    }
    // 检测此监听范例的事宜行列
    // 不存在则直接返回
    if (!ctx) {
      return false;
    } else if (isFunction(ctx)) {
      // 是甘薯则直接实行,并将一切参数通报给此函数(回调函数)
      ctx.apply(this, args);
    } else if (isArray(ctx)) {
      // 是数组则遍历挪用
      for (var i = 0; i < ctx.length; i++) {
        ctx[i].apply(this, args);
      }
    }
};

emit从邃晓水平上来讲应当是更轻易一些,只是从存储事宜的对象中找到响应范例的监听事宜行列,然后实行行列中的每一个回调

removeListener 要领完成

event.prototype.removeListener = function () {};

删除某种监听范例的某一个监听回调,明显,我们依然须要一个事宜type,以及一个监听回调,当事宜对列中的回调与该回调雷同时,则移除

event.prototype.removeListener = function (eventName, content) {};

须要注重的是,假如我们确切存在要移除某个监听事宜的回调,在on要领时肯定不要运用匿名函数作为回调,如许会致使在removeListener是没法移除,由于在javascript中匿名函数是不相称的。

// 假如须要移除

// 毛病
event.on('eatting', function (msg) {

});

// 准确
event.on('eatting', cb);
// 回调
function cb (msg) {
    ...
}
event.prototype.removeListener = function (eventName, content) {
    var _event, ctx, index = 0;
    _event = this.event_list;
    if (!_event) {
      return this;
    } else {
      ctx = this.event_list[eventName];
    }
    if (!ctx) {
      return this;
    }
    // 假如是函数  直接delete
    if (isFunction(ctx)) {
      if (ctx === content) {
        delete _event[eventName];
      }
    } else if (isArray(ctx)) {
      // 假如是数组 遍历
      for (var i = 0; i < ctx.length; i++) {
        if (ctx[i] === content) {
          // 监听回调相称
          // 从该监听回调的index最先,背面的回调顺次覆蓋掉前面的回调
          // 将末了的回调删除
          // 等价于直接将满足前提的监听回调删除
          this.event_list[eventName].splice(i - index, 1);
          ctx.ListenerCount = ctx.length;
          if (this.event_list[eventName].length === 0) {
            delete this.event_list[eventName]
          }
          index++;
        }
      }
    }
};

removeAllListener 要领完成

event.prototype.removeAllListener = function () {};

此要领有两个用处,即完成当有参数事宜范例eventName时,则删除该范例的一切监听(清空此事宜的监听回调行列),当没有参数时,则将一切范例的事宜监听对垒悉数移除,照样比较好邃晓的直接上代码

event.prototype.removeAllListener = function ([,eventName]) {
    var _event, ctx;
    _event = this.event_list;
    if (!_event) {
      return this;
    }
    ctx = this.event_list[eventName];
    // 推断是不是有参数
    if (arguments.length === 0 && (!eventName)) {
      // 无参数
      // 将key 转成 数组  并遍历
      // 顺次删除一切的范例监听
      var keys = Object.keys(this.event_list);
      for (var i = 0, key; i < keys.length; i++) {
        key = keys[i];
        delete this.event_list[key];
      }
    }
    // 有参数 直接移除
    if (ctx || isFunction(ctx) || isArray(ctx)) {
      delete this.event_list[eventName];
    } else {
      return this;
    }
};

其重要完成思绪大抵如上所述,貌似还漏了一些什么,哦,是关于是不是凌驾舰艇数目的最大限定的处置惩罚
在on要领中

...
// 检测回调行列是不是有maxed属性以及是不是为false
if (!ctx.maxed) {
      //只要在是数组的情况下才会做比较
      if (isArray(ctx)) {
        var len = ctx.length;
        if (len > (this.MaxEventListNum ? this.MaxEventListNum : this.defaultMaxEventListNum)) { 
        // 当凌驾最大限定,则会发除正告
          ctx.maxed = true;
          console.warn('events.MaxEventListNum || [ MaxEventListNum ] :The number of subscriptions exceeds the maximum, and if you do not set it, the default value is 10');
        } else {
          ctx.maxed = false;
        }
      }
    }

...

如今Vue可谓是红的发紫,没紧要,events-manage也能够在Vue中挂在到全局运用哦

events.prototype.install = function (Vue, Option) {
    Vue.prototype.$ev = this;
  }

不必多诠释了吧,想必看官都邃晓应当怎样运用了吧(在Vue中)

关于本库更细致更细致的运用文档,赶忙戳这里

码字不容易啊,假如以为对您有一些协助,还请给一个大大的赞👍哈哈

(…已经是凌晨…)

    原文作者:润物无声
    原文地址: https://segmentfault.com/a/1190000015808264
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞