#javascript #dom-events
#javascript #dom-события
Вопрос:
Допустим, у меня есть такой код:
let myFunc = () => {
console.log("hello");
}
document.addEventListener("click", myFunc);
document.addEventListener("click", myFunc);
document.addEventListener("click", myFunc);
document.addEventListener("click", myFunc);
Почему при нажатии на документ консоль регистрируется только один раз? Я не возражаю против такого поведения, но мне просто любопытно, как это реализовано.
Например, если вы сделали что-то вроде этого:
let events = {};
function addEventListener(key, callback) {
if (!key) { return; }
if (!events.hasOwnProperty(key)) {
events[key] = {};
}
events[key][callback] = callback;
}
Тогда вы используете функцию в качестве ключа, но разве для ключей допустимы не только строки? Как JavaScript однозначно идентифицирует функции, чтобы он знал, что не следует добавлять одну и ту же функцию несколько раз?
Ответ №1:
Данный прослушиватель событий с определенной конфигурацией может быть добавлен к элементу только один раз — если вы добавляете его несколько раз, как вы можете видеть, это будет так, как если бы был добавлен только один прослушиватель. Это описано в спецификации здесь:
- Если список прослушивателей событий EventTarget не содержит прослушивателя событий, тип которого является типом прослушивателя, обратный вызов — это обратный вызов прослушивателя, а захват — это захват прослушивателя, затем добавьте прослушиватель в список прослушивателей событий EventTarget.
Чтобы расширить это, для слушателя, который считается таким дубликатом:
чей тип является типом слушателя
ссылается на имя события, например 'click'
обратный вызов — это обратный вызов слушателя
это должна быть та же ссылка на функцию ( ===
на добавленный ранее прослушиватель)
захват — это захват слушателя
относится к тому, слушает ли слушатель на этапе захвата или на этапе пузырьковой обработки. (Это задается третьим логическим параметром addEventListener
, который по умолчанию true
имеет значение — bubbling , или с { capture: boolean }
в качестве третьего аргумента)
Если все вышеперечисленные функции совпадают с функциями прослушивателя, добавленного ранее, то новый прослушиватель будет считаться дубликатом и не будет добавлен снова.
Простой способ добавить такого слушателя несколько раз, если вы хотите, — это выполнить встроенный обратный вызов, который вызывает вашего слушателя:
let myFunc = () => {
console.log("hello");
}
document.addEventListener("click", () => myFunc());
document.addEventListener("click", () => myFunc());
document.addEventListener("click", () => myFunc());
document.addEventListener("click", () => myFunc());
click me
Вышеуказанное будет работать, потому что обратные вызовы, переданные addEventListener
не равны: () => myFunc()
не ===
() => myFunc()
является .
Комментарии:
1. Да, мне любопытно, как это реализовано. Как Javascript узнает, что обратный вызов прослушивателя событий уже добавлен? Чтобы уточнить, я не хочу, чтобы функция запускалась 4 раза, я только хочу знать, как они однозначно идентифицируют функции.
2. Смотрите цитату из спецификации в ответе. Если список прослушивателей событий EventTarget не содержит прослушивателя событий, тип которого является типом прослушивателя, обратный вызов — это обратный вызов прослушивателя, а захват — это захват прослушивателя, затем добавьте прослушиватель в список прослушивателей событий EventTarget. Если тот же прослушиватель был добавлен ранее, то в списке прослушивателей событий будет такой идентичный элемент, поэтому новый прослушиватель не будет добавлен.
3. @RyanPeschel Функции являются объектами. Вы можете сравнить их с
===
. Браузер выполняет это внутренне вaddEventListener
4. Таким образом, у них просто есть массив функций, и они просто перебирают существующие, когда вы добавляете новую, чтобы убедиться, что она еще не добавлена? Есть ли какой-либо способ избежать O (n) этого? Я надеялся избежать повторения существующего списка, поэтому я изначально думал об использовании объектов.
5. @RyanPeschel
Map
также существует, но спецификация W3C дляaddEventListener
существовала задолго до того,Map
как стала частью ECMAScript.
Ответ №2:
Концептуально реализация может быть чем-то вроде этого (я игнорирую детали спецификации, не относящиеся к вопросу):
function addEventListener(type, listener, useCapture = false) {
let typeListeners = this.eventListeners[type];
if (!typeListeners) {
this.eventListeners[type] = [{function: listener, useCapture: useCapture}];
} else {
let found = typeListeners.find(l => l.function === listener amp;amp; l.useCapture == useCapture);
if (!found) {
typeListeners.push({function: listener, useCapture: useCapture});
}
}
}
Он выполняет поиск в списке прослушивателей типа события, существующего соответствия функции и useCapture
параметров. Если этого еще нет, он добавляет его.
Комментарии:
1. Правильно, здесь используется массив. Возможно ли это сделать с объектами, чтобы вы могли получить извлечение O (1)?
2. Это может быть, но зачем беспокоиться? Необычно иметь несколько прослушивателей для одного и того же события в элементе, и очень редко их так много, что линейный поиск будет проблемой производительности.
3. Я бы ожидал, что в 90% случаев алгоритм хеширования будет дороже, чем линейный.
4. Да, я согласен. Я, вероятно, собираюсь использовать linear, потому что это, вероятно, будет быстрее, а также проще. Мне просто интересно, можно ли однозначно идентифицировать функции в объекте. Похоже, мне нужно будет использовать Map или Set. Приятно знать, спасибо!
5. @RyanPeschel Также необходимо проверить
useCapture
флаг.