Вызов из "соседней функции"
Есть две функции one() и two(). В функции one() устанавливается слушатель на элемент. В функции two() его надо снять, когда происходит событие в функции two(). То есть каким образом я могу получить ссылку на doSomething(), которая находится в функции one() из функции two()
function one() {
// something is running
function doSomething() {
// doSomething
}
elDo.addEventListener('click', doSomething)
}
function two() {
one()
function stopAll() {
// doSomething
// Как здесь убрать слушатель с elDo???
}
elStop.addEventListener('click', stopAll)
}
two()
Ответы (3 шт):
const btn1 = document.getElementById('btn1');
const btn2 = document.getElementById('btn2');
function one() {
btn1.addEventListener('click', function() { console.log('click one'); });
}
function two() {
btn1.oldAddEventListener = btn1.addEventListener;
btn1.addEventListener = function(a1, a2, a3) {
this.eventToRemove = { a1, a2, a3 };
this.oldAddEventListener(a1, a2, a3);
}
one();
btn1.addEventListener = btn1.oldAddEventListener;
delete btn1.oldAddEventListener;
btn1.addEventListener('click', function() {
console.log('click two');
this.removeEventListener(btn1.eventToRemove.a1, btn1.eventToRemove.a2, btn1.eventToRemove.a3);
});
}
btn2.addEventListener('click', two);
<button id="btn1">Click</button><button id="btn2">Remove After Click</button>
const elDo = document.getElementById('bdo');
const elStop = document.getElementById('bstop');
function one() {
function doSomething() {
console.log('doSomething');
}
elDo.addEventListener('click', elDo.handler = doSomething);
}
function two() {
one();
function stopAll() {
console.log('stopAll');
elDo.removeEventListener('click', elDo.handler);
}
elStop.addEventListener('click', stopAll);
}
two();
<button id="bdo">One</button>
<button id="bstop">Two</button>
Не кидайтесь камнями, задачу автора вопроса решает.
Получить доступ к локальной переменной или функции "снаружи" - невозможно.
Самым простым вариантом решения, в данном случае, является сохранение ссылки на слушатель события в чем-то внешнем по отношению к one и two: либо в переменной вне обеих функций (на уровне их объявления, или выше); либо в свойстве объекта (для этого можно и DOM-объект, и объект функции использовать); либо в свойстве window, что аналогично глобальной переменной.
Вариант со свойством window:
function one() {
/* .... */
window._elDoHandler = elDo.addEventListener('click', doSomething);
/* .... */
}
function two() {
/* .... */
function stopAll() {
if (window._elDoHandler)
elDo.removeEventListener('click', window._elDoHandler);
}
/* .... */
}
Ну а при хранении ссылки в свойстве самого DOM-объекта, вместо window._elDoHandler - использовать (например) elDo._clickHandler. А вариант использования внешней переменной, и без примера должен быть ясен.
Еще можно организовать "автоматическое" хранение всех добавленных слушателей в кастомном свойстве eventListeners... Реализация обновлений свойства через перекрытие прототипных методов - довольно простая (хоть и весьма многобуквенная):
/* этот вызов просто прячет пугающе "пухлую" реализацию,
* он для ее перемещения в конец примера
* (т.к. при реальном использовании таких вещей, их
* подключают к документу в <head>, как и полиффилы). */
overrideListenerMethods();
// выберем из документа элементики...
const elDo = document.querySelector('#do'),
elStop = document.querySelector('#stop');
// ...и далее - применение нашего кастомного свойства в коде из вопроса
function one() {
console.log('something is running');
function doSomething() {
console.log('doSomething');
}
elDo.addEventListener('click', doSomething);
// вывод всех слушателей события 'click' из свойства eventListeners (просто демо)
console.log('[add] listeners:', ...elDo.eventListeners.click.entries());
}
function two() {
one();
elStop.addEventListener('click', stopAll);
function stopAll() {
// удаление всех слушателей события 'click' на элементе elDo
for (const listener of elDo.eventListeners.click.keys())
elDo.removeEventListener('click', listener);
// ну и затем опять вывод слушателей, точнее их отсутствия :)
console.log('[remove] listeners:', ...elDo.eventListeners.click.entries());
}
}
two();
// ------------------------------------------------
function overrideListenerMethods() {
// перекрытие addEventListener
EventTarget.prototype._addEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, listener, option) {
const capture = Boolean(typeof option === 'object' ? option.capture : option),
listenersKey = capture ? 'capturingEventListeners' : 'eventListeners',
listeners = this[listenersKey] = (this[listenersKey] || Object.create(null)),
listenersForType = listeners[type] = (listeners[type] || new Map()),
handler = listener.handleEvent || listener;
if (listenersForType.has(listener)) return listenersForType.get(handler);
listenersForType.set(listener, { option, /* можно хранить что-то еще. Например, кастомный tag обработчика, или время добавления, да что угодно */ });
this._addEventListener(type, listener, option);
return handler; // это добавлено для возможности вызова обработчика сразу после его добавления слушателем события (нередко такое требуется)
};
// перекрытие removeEventListener
EventTarget.prototype._removeEventListener = EventTarget.prototype.removeEventListener;
EventTarget.prototype.removeEventListener = function (type, listener, option) {
const capture = Boolean(typeof option === 'object' ? option.capture : option),
listenersKey = capture ? 'capturingEventListeners' : 'eventListeners',
listenersForType = this[listenersKey]?.[type];
if (listenersForType instanceof Map)
listenersForType.delete(listener);
this._removeEventListener(type, listener, option);
return listenersForType; // это для возможности вызвать по цепочке какой-то другой метод хранилища, после удаления слушателя
};
}
<button id="do">Do</button>
<button id="stop">Stop</button>
Тут в качестве хранилища использован объект с вложенными Map, т.к. перечисление слушателей (используемых в роли ключей) отдельно от хранимых параметров может быть удобным. Плюс, удобная проверка наличия слушателя методом has, и сохранение порядка добавления слушателей. Конечно, для надежности стоит перекрыть возможность вызова Map-методов add и delete из внешнего кода, или даже описать все через кастомные типы... но это всего лишь пример, на уровне идеи. Его развитие/переработка это дело вкуса.
Зачем используется оберточный объект - для того чтобы слушатели сгруппировать по типу (имени) события. То есть, evtTarget.eventListeners[типСобытия] возвращает Map-коллекцию слушателей событий указанного типа (при ее наличии, конечно): это слегка подслащивает синтаксис. Слушатели для этапов всплытия и захвата хранятся отдельно, по понятным причинам.
Для соответствия стандартным методам, реализация в примере поддерживает в качестве слушателей как функции, так и объекты интерфейса EventListener.
Поведение при повторном добавлении слушателей, а именно логика "ссылочных дубликатов" - тоже соответствует стандартному методу (см. раздел «Multiple identical event listeners» в статье MDN об addEventListener.