Обработчик события вызывается несколько раз при динамическом создании

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

Другие вопросы по тегам