Получился достаточно компактный и производительный код. Вот как можно с этим работать:
//Сначала добавляем нужные методы к объекту
var x = {};
setAddEvent(x);
//Затем добавляем событие. Называем его 'event1'. На выходе получаем функцию, которую нужно будет вызывать, когда событие произойдёт
var f = x.addEvent('event1');
// Теперь добавляем обработчики события - способ стандартный (такой же, как принятый в DOM)
var y = addEventListener('event1', function(){ console.log('Событие 1 произошло!'); }),
z = addEventListener('event1', function(){ console.log('Событие 1 произошло! (Второй обработчик)'); }, false);
// Вызываем событие
f(); // "Событие 1 произошло!" и "Событие 1 произошло! (Второй обработчик)"
// Удаляем один из обработчиков
removeEventListener('event1', y, false)
// Снова вызываем событие
f(); // "Событие 1 произошло! (Второй обработчик)"
// Удаляем последний обработчик
removeEventListener('event1', z, false);
// Вызываем событие
f(); // Ничего не выводит
// Удаляем событие
removeEvent('event1');
// Пробуем вызвать событие
try { f(); } catch(/** @type Error */ e) { console.error(e.message); } // Выведет в консоль сообщение об ошибке: "Событие 'event1' было удалено!"
Примерно так. Добавлю ещё, что функция removeEventListener
возвращает значение типа boolean
, означающее наличие, либо отсутствие иных обработчиков данного события в настоящий момент.
Без лишних предисловий, вот основной код функции:/** Функция делает переданный ей объект Observer`ом, т.е. генератором событий, на которые можно подписываться и
* отписываться. После её вызова у переданного ей объекта появляются 3 метода: <code>addEvent(name)</code> для
* добавления событий и пара функций <code>{@link #addEventListener}</code> и
* <code>{@link #removeEventListener}</code> для добавления и удаления обработчиков событий.
* @param {Object} that любой объект JavaScript, который нужно сделать Observer`ом.
* @returns {Object} переданный в качестве аргумента, обогащённый методами <code>{@link #addEvent}</code>,
* <code>{@link #addEventListener}</code> и <code>{@link #removeEventListener}</code> объект
* <code><em>that</em></code>. */
function setAddEvent(that) {
var /** @type Object<Object<Function[]>>*/ events = {};
/** Цепляет к объекту событие, на которое после этого можно будет вешать обработчики при помощи метода
* <code>{@link #addEventListener}</code>.
* @param {string[]} name имя события (которое потом нужно будет передавать первым параметром - type - в метод
* <code>addEventListener</code>).
* @returns function(string, boolean) функция, которую нужно вызывать в ответ на событие, передавая ей при
* вызове объект события. Функция будет вызывать все слушатели по порядку, передавая им этот объект. */
that.addEvent = function(name) {
if (name in events)
throw new Error('Cобытие ' + name + ' уже есть!');
/** @type Function[] */ (
/** @type Object<Function[]> */ events[name] = {}
)[true] = [];
/** @type Function[] */ events[name][false] = [];
return function(event, isPropagation) {
if (!(name in events)) throw new Error('Событие \'' + name + '\' было удалено!');
if (typeof isPropagation === 'undefined') isPropagation = false;
for (var /** @type Function[] */ listeners = events[name][isPropagation],
/** @type number */ i = 0,
/** @type Function */ listener;
listener = listeners[i++];)
listener(event);
};
};
/** Удаляет событие и всех его слушателей из объекта.
* @param {string} name Имя события, назначенное ему ранее при вызове метода <code>{@link #addEvent}</code>.*/
that.removeEvent = function(name) {
if (!(name in events))
throw new Error(
'События ' + name + ' уже итак нет, либо оно не было создано при помощи метода \'addEvent\'!');
delete events[name];
};
/** Добавляет к объекту слушателя события, ранее созданного при помощи вызова метода
* <code>{@link #addEvent}</code>. Если метод <code>addEventListener</code> у объекта уже есть, данный метод
* выступает в качестве его Proxy, сохраняя его в замыкании и вызывая его для событий, которые не были созданы
* при помощи вызова метода <code>{@link #addEvent}</code>.
* @param {string} eventName Имя события объекта
* @param {function(Event)} handler Обработчик события
* @param {boolean} [isPropagation=false]
* @returns {function(Event)} Параметр, переданный функции вторым - handler. */
that.addEventListener = function(realSubject) {
return function(eventName, handler, isPropagation){
if (typeof isPropagation === 'undefined') isPropagation = false;
if (eventName in events)
events[eventName][isPropagation].push(handler);
else
if (typeof realSubject === 'function')
realSubject.apply(this, arguments);
return handler;
};
}(that.addEventListener);
/** Удаляет из объекта обработчик события, ранее созданного при помощи вызова метода
* <code>{@link #addEvent}</code>. Если метод <code>removeEventListener</code> у объекта уже есть, данный метод
* выступает в качестве его Proxy, сохраняя его в замыкании и вызывая его для событий, которые не были созданы
* при помощи вызова метода <code>{@link #addEvent}</code>.
* @param {string} eventName Имя события объекта
* @param {function(Event)} handler Обработчик события
* @param {boolean} [isPropagation=false]
* @returns {boolean} Остались ли ещё обработчики для данного события? Возврат значения <code>false</code>
* указывает на бессмысленность дальнейшего вызова функции, возвращённой методом <code>{@link #addEvent}</code>
* при создании данного события - пока при помощи метода <code>{@link #addEventListener}</code> не будет
* прикреплён хотя бы один новый обработчик. */
that.removeEventListener = function(realSubject) {
return function(eventName, handler, isPropagation){
if (typeof isPropagation === 'undefined') isPropagation = false;
var /** @type Function[] */ listeners,
/** @type number */ index;
if (eventName in events &&
(index = (listeners = events[eventName][isPropagation]).indexOf(handler)) !== -1)
listeners.splice(index, 1);
else
if (typeof realSubject === 'function')
realSubject.apply(this, arguments);
return listeners.length > 0 || events[eventName][!isPropagation].length > 0;
};
}(that.removeEventListener);
return that;
}
//Test
var f = setAddEvent(this).addEvent('event1'),
y = addEventListener('event1', function(){ console.log('Событие 1 произошло!'); }),
z = addEventListener('event1', function(){ console.log('Событие 1 произошло! (Второй обработчик)'); }, false);
f();
addEventListener('load', function(){ console.log('loaded'); }, false);
console.log( 'Остались ли ещё обработчики? - ' + (
removeEventListener('event1', y, false)
? 'Да': 'Нет'
));
f();
console.log( 'Остались ли ещё обработчики? - ' + (
removeEventListener('event1', z, false)
? 'Да': 'Нет'
));
f();
removeEvent('event1');
try { f(); } catch(/** @type Error */ e) { console.error(e.message); }
Комментариев нет:
Отправить комментарий