Управление событиями JS

внешний интерфейс JavaScript Vue.js

управление событиями на основе js (подписка-публикация) ---event-mange

О мероприятии

Когда мы используем javascript для разработки, мы часто используем множество событий, таких как щелчок, клавиатура, мышь и т. д., эти физические события. То, что мы сегодня называем событием, — это другая форма события, подписка-публикация, также известная как шаблон наблюдателя, который определяет зависимость «один ко многим», когда состояние объекта изменяется, все объекты, которые зависят от него, получают уведомления, и в javascript вместо модели публикации-подписки обычно используется модель событий.

Приведите пример из реальной жизни, который поможет вам понять эту закономерность. Жарким летом моя мама варила рис и подавала его на стол горячим паром.В это время моя мама позвала Сяо Мина поесть (Сяо Мин был голоден в соседней комнате и ел курицу на ночь...), Сяо Мин вышел посмотреть и сказал своей матери Саиду, подожди немного, "рис холодный", а потом позвони мне, слишком жарко... десять минут спустя... моя мама позвонила тебе: "рис холодный". ', иди и ешь, а затем Сяо Мин услышала крик своей матери: 'Еда была холодной, поэтому я быстро вышла и доела ее. Этот пример представляет собой описанную выше модель подписки-публикации. В этом примере Сяо Мин — подписчик (подписка на «Рис холодный»), а мама — издатель (публикация сигнала «Рис холодный»).

У подписно-публикационной модели есть очевидные преимущества: подписчику не нужно постоянно спрашивать веб-мастера, остыла ли еда, в соответствующий момент события веб-мастер уведомит этих подписчиков о том, что еда остыла, и они Вы можете прийти и поесть. Таким образом, нет необходимости заставлять Сяомина и его мать вместе.Когда младшие братья и сестры Сяомина хотят есть, когда еда остыла, им нужно только сказать его матери. Точно так же, как подписка, с которой должен соприкасаться каждый наблюдатель --- публикация: привязка событий DOM

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

Вернемся к теме:

event-mangeРеализовано через модель подписки-публикации

шаг за шагом

event-mangeосновной модульметод:

  • on: Подписчик, добавить события
  • эмулировать: издатель, генерировать событие
  • один раз: подписчик, добавьте события, которые можно отслеживать только один раз, а затем срок их действия истечет.
  • removeListener: удаляет одну подписку (событие)
  • removeAllListener: удаляет подписку для одного типа события или удаляет все подписки.
  • getListenerCount: получить количество подписчиков

event-mangeосновной модульАтрибуты:

  • MaxEventListNum: установите максимальное количество подписчиков для одного события (по умолчанию 10).

основной скелет

Прежде всего, мы хотим подписаться и опубликовать через event.on и event.emit, создать экземпляр события через конструктор, а on и emit — два метода этого экземпляра соответственно, Аналогично, все основные методы, перечисленные выше, Оба являются методами-прототипами объекта события.

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 () {};

Кажется, чего-то не хватает, да, именно свойства MaxEventListNum объекта события, который мы перечислили выше, мы его добавим к нему

function event () {
    //因为MaxEventListNum属性是可以让开发者设置的
    //所以在没有set的时候,我们将其设置为 undefind
    this.MaxEventListNum = this.MaxEventListNum || undefined;

    //如果没有设置set,我们不能让监听数量无限大
    //这样有可能会造成内存溢出
    //所以我们将默认数量设置为10(当然,设置成别的数量也是可以的)
    this.defaultMaxEventListNum = 10;
}

На данный момент, в основном, начальное состояние свойств и методов модуля управления временем, которого мы хотим достичь, почти такое же, то есть, когда скелет выходит, нам нужно заполнить его логику кода, чтобы он стал плотным и кровь (похоже на жизнь...)

О чем стоит подумать, так это о том, что мы закончили построение скелета, и что нам нужно сделать, это модель подписки-публикации.Как мы должны помнить многочисленные события подписки? В первую очередь для подписки нам нужно иметь тип подписки то есть топик.Для этого топика нам нужно собрать воедино все события которые подписываются на этот топик.Да можно выбрать Массив,начальная конструкция

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

Затем мы поместим список событий, в котором хранится наше событие, в код, чтобы улучшить его как атрибут события.

function event () {
    // 这里我们做一个简单的判断,以免一些意外的错误出现
    if(!this.event_list) {
        this.event_list = {};
    }

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

по внедрению метода
event.prototype.on = function () {};

В результате анализа делается вывод, что метод on должен сначала получить подписанную тему, а затем метод обратного вызова, который срабатывает, когда тема отвечает.

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

eventName используется в качестве типа события и используется в качестве атрибута event_list.Все прослушиватели, чей тип события — eventName, помещаются в массив 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;
    }
    ...
};
один раз реализация метода
event.prototype.once = function () {};

Метод OnCE выполняет только подписанное событие, затем сразу же удаляется функция обратного вызова подписки в соответствующем атрибуте типа подписки в Event_LIST, и хранимая процедура почти соответствует методу ON.Также нужен тип подписки Topic и один Tonance Содержимое ответного события

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

Отмените регистрацию подписки сразу после выполнения этого обратного вызова события, и если в это время для одного и того же типа события зарегистрировано несколько обратных вызовов прослушивателя, мы не можем точно удалить обратный вызов прослушивателя, зарегистрированный текущим однократным методом, поэтому мы обычно используем событие обхода. в очередь, поиск соответствующего обратного вызова прослушивателя и его удаление не работает. К счастью, великолепный язык javascript предоставляет нам мощную функцию замыкания, которую можно использовать для декорирования контента и превращения его в совершенно новую функцию.

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 используется для публикации событий и управления выполнением соответствующих обратных вызовов прослушивателя в очереди прослушивателя событий, поэтому нам нужна тема типа события.

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 должно быть проще для понимания, просто найдите соответствующий тип очереди событий прослушивания из объекта, в котором хранится событие, а затем выполните каждый обратный вызов в очереди.

Передалить способ достижения
event.prototype.removeListener = function () {};

Удаляем обратный вызов слушателя определенного типа слушателя.Очевидно, нам все еще нужен тип события и обратный вызов слушателя.Когда обратный вызов в столбце пары событий совпадает с обратным вызовом, удалите его

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;
    }
};

Основная идея реализации примерно такая, как описано выше, и кажется, что чего-то не хватает, это обработка того, превышено ли максимальное количество кораблей. в методе включения

...
// 检测回调队列是否有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 можно назвать красным и фиолетовым, это не имеет значения, управление событиями также можно использовать глобально во Vue.

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

Объяснять не надо, видимо чиновники понимают как им пользоваться (во Vue)

Для получения более конкретной и подробной документации по использованию этой библиотеки,кликните сюда

Кодировать слова непросто, если вы думаете, что это вам поможет, пожалуйста, поставьте лайк👍ха-ха

(...раннее утро...)