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