Обработчик события вызывается несколько раз при динамическом создании
Я ищу лучшее решение моей проблемы.
У меня есть функция, которая возвращает функцию, которая динамически присоединяет обработчики событий к документу, она обернута внутри init
функция:
const init = (events) => {
const generateListener = listenForKey => ({ keyCode, key}) => {
if (keyCode === listenForKey) {
console.log(`action - ${key}`);
}
}
events.forEach(({name, keyCode}) => {
document.addEventListener(name, generateListener(keyCode))
});
}
Теперь, когда я запускаю этот код, он работает как положено:
const someEvents = [
{
name: 'keypress',
keyCode: 13
},
{
name: 'keypress',
keyCode: 43
}
];
init(someEvents);
Проблемы начинаются, когда мне нужно бежать init
еще раз (что мне нужно):
init(someEvents);
// ... at a future point in time
init(someEvents);
В этом случае обработчик будет вызван дважды, потому что он был зарегистрирован дважды.
Это сделано потому, что браузер считает, что передаются новые данные, так как каждый раз я передаю новый экземпляр функции.
Вы можете увидеть это здесь (просто нажмите Enter
или же +
ключи):
const init = (events) => {
const generateListener = listenForKey => ({ keyCode, key}) => {
if (keyCode === listenForKey) {
console.log(`action - ${key}`);
}
}
events.forEach(({name, keyCode}) => {
document.addEventListener(name, generateListener(keyCode))
});
}
const someEvents = [
{
name: 'keypress',
keyCode: 13
},
{
name: 'keypress',
keyCode: 43
}
];
init(someEvents);
// this will register the events again and will invoke the handlers twice
//as we are passing "new" argument (a new function instance)
init(someEvents);
<div>Click the '+' or 'Enter' keys </div>
Одно из возможных решений - использовать параметр объекта или сделать что-то похожее на меня, например, сохранить обработчики в окне и прикрепить их к документу как одному экземпляру. Таким образом, браузер будет игнорировать несколько регистраций для одного и того же обработчика:
const init2 = (events) => {
const generateListener = listenForKey => ({ keyCode, key}) => {
if (keyCode === listenForKey) {
console.log(`action - ${key}`);
}
}
events.forEach(({name, keyCode}) => {
const handlerName = `on${name}${keyCode}`;
this[handlerName] = this[handlerName] || generateListener(keyCode);
document.addEventListener(name, this[handlerName]);
});
}
const someEvents = [
{
name: 'keypress',
keyCode: 13
},
{
name: 'keypress',
keyCode: 43
}
];
init2(someEvents);
init2(someEvents);
Это хорошо работает, но мы загрязняем объект глобального окна.
Я чувствую, что могу что-то здесь упустить, поэтому я ищу лучший подход.
1 ответ
Согласно MDN, несколько прослушивателей событий с одинаковыми параметрами и с одной и той же целью отбрасываются.
Ваша проблема связана с тем, что вы выполняете регистрацию через анонимные функции (стрелки).
Теперь, поскольку у вас уже есть глобальный объект (т.е. вам не нужно создавать больше глобальных объектов), вы можете использовать его для создания именованных функций, таких как:
const someEvents = [
{
name: 'keypress',
keyCode: 13,
onkeypress13 () {...}
},
{
name: 'keypress',
keyCode: 43,
onkeypress43 () {...}
}
];
и это должно заботиться о двойных регистрациях. Вот рабочий фрагмент (без проверки ключевого кода, если условие, но он показывает, что каждый обработчик события вызывается только один раз)
const init = (events) => {
events.forEach((event) => {
document.addEventListener(event.name, event[`on${event.name}${event.keyCode}`])
});
}
const someEvents = [{
name: 'keypress',
keyCode: 13,
onkeypress13() {
console.log('13 pressed')
}
},
{
name: 'keypress',
keyCode: 43,
onkeypress43() {
console.log('43 pressed')
}
}
];
init(someEvents);
// this will register the events again and will invoke the handlers twice
//as we are passing "new" argument (a new function instance)
init(someEvents);
<div>Click the '+' or 'Enter' keys </div>