Spec-Zone.ru › Web APIs

EventTarget: метод addEventListener()

Базовая поддержка Широко поддерживается *

Эта функция хорошо зарекомендовала себя и работает на многих устройствах и версиях браузеров. Она доступна в браузерах с июля 2015 года.

* Некоторые части этой функции могут иметь разный уровень поддержки.

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

Примечание: Эта функция доступна в Веб-воркерах.

Метод addEventListener() интерфейса EventTarget настраивает функцию, которая будет вызываться всякий раз, когда указанное событие будет доставлено целевому объекту.

Обычные цели — Element или его потомки, Document и Window, но целью может быть любой объект, поддерживающий события (например, IDBRequest).

Примечание: Метод addEventListener() является рекомендуемым способом регистрации обработчика событий. Преимущества заключаются в следующем:

  • Он позволяет добавить несколько обработчиков для одного события. Это особенно полезно для библиотек, модулей JavaScript или любого другого кода, который должен хорошо работать с другими библиотеками или расширениями.
  • В отличие от использования свойства onXYZ, он предоставляет более точный контроль над фазой активации обработчика (захват или всплытие).
  • Он работает с любым целевым объектом, а не только с HTML- или SVG-элементами.

Метод addEventListener() добавляет функцию или объект, реализующий метод handleEvent(), в список обработчиков событий для указанного типа события на целевом объекте EventTarget. Если функция или объект уже есть в списке обработчиков событий для этой цели, то она не добавляется повторно.

Примечание: Если конкретная анонимная функция есть в списке обработчиков событий, зарегистрированных для определённого объекта, и затем в коде позже анонимная функция с идентичным кодом, но новым экземпляром, указана в вызове addEventListener, то вторая функция также будет добавлена в список обработчиков событий для этого объекта.

Действительно, анонимные функции не идентичны даже если определены с помощью того же неизменного исходного кода, вызываемого повторно, даже если в цикле.

Повторное определение той же безымянной функции в таких случаях может быть проблематично. (См. Проблемы с памятью ниже.)

Если обработчик события добавлен к EventTarget изнутри другого обработчика — то есть, во время обработки события — это событие не вызовет новый обработчик. Однако новый обработчик может быть вызван на более поздней стадии потока события, например, во время фазы всплытия.

Синтаксис

addEventListener(type, listener)
addEventListener(type, listener, options)
addEventListener(type, listener, useCapture)

Параметры

type

Строка, чувствительная к регистру, представляющая тип события для прослушивания.

listener

Объект, который получает уведомление (объект, реализующий интерфейс Event) при возникновении события указанного типа. Это должен быть null, объект с методом handleEvent(), или функция JavaScript. Подробнее о самом обратном вызове см. в разделе Обратный вызов обработчика события.

options Необязательно

Объект, который задаёт характеристики обработчика событий. Доступные параметры:

capture Необязательно

Булево значение, указывающее, что события этого типа будут обработаны зарегистрированными listener до обработки любыми EventTarget ниже по дереву DOM. Если не указано, по умолчанию false.

once Необязательно

Булево значение, указывающее, что обработчик listener будет вызван не более одного раза после добавления. Если true, обработчик listener будет автоматически удалён при вызове. Если не указано, по умолчанию false.

passive Необязательно

Булево значение, которое, если true, указывает, что функция, указанная listener, никогда не будет вызывать preventDefault(). Если пассивный обработчик вызывает preventDefault(), ничего не произойдёт, и может быть выведено предупреждение в консоль.

Если этот параметр не указан, он устанавливается по умолчанию в false — за исключением браузеров, кроме Safari, где он устанавливается по умолчанию в true для событий wheel, mousewheel, touchstart и touchmove. Подробнее см. Использование пассивных обработчиков.

signal Необязательно

AbortSignal. Обработчик будет удалён, когда метод abort() контроллера прерывания AbortController, который владеет AbortSignal, будет вызван. Если не указано, обработчик не связан с AbortSignal.

useCapture Необязательно

Булево значение, указывающее, будут ли события этого типа обработаны зарегистрированными listener до обработки любыми EventTarget ниже по дереву DOM. События, которые всплывают вверх по дереву, не вызовут обработчик, предназначенный для захвата. Всплытие и захват — два способа распространения событий, возникающих в элементе, вложенном в другой элемент, когда оба элемента зарегистрировали обработчик для этого события. Режим распространения события определяет порядок, в котором элементы получают событие. Подробнее см. DOM Level 3 Events и Порядок событий JavaScript. Если не указано, по умолчанию useCapture — false.

Примечание: Для обработчиков событий, прикреплённых к целевому объекту, событие находится в фазе целевого объекта, а не в фазах захвата и всплытия. Обработчики событий в фазе захвата вызываются до обработчиков событий в фазах целевого объекта и всплытия.

wantsUntrusted Необязательно Нестандартный

Специфический параметр Firefox (Gecko). Если true, обработчик получает синтетические события, отправленные веб-содержимым (по умолчанию false для браузера chrome и true для обычных веб-страниц). Этот параметр полезен для кода в дополнениях, а также для самого браузера.

Возвращаемое значение

None (undefined).

Примечания по использованию

Обратный вызов обработчика события

Обработчик события может быть указан как функция обратного вызова или объект, метод handleEvent() которого служит функцией обратного вызова.

Функция обратного вызова сама имеет те же параметры и возвращаемое значение, что и метод handleEvent(); то есть, обратный вызов принимает один параметр: объект, основанный на Event, описывающий произошедшее событие, и не возвращает ничего.

Например, обработчик событий, который может обрабатывать как fullscreenchange, так и fullscreenerror, может выглядеть так:

function handleEvent(event) {
  if (event.type === "fullscreenchange") {
    /* handle a full screen toggle */
  } else {
    /* handle a full screen toggle error */
  }
}

Значение «this» в обработчике

Часто бывает желательно сослаться на элемент, на котором обработчик события был вызван, например, при использовании универсального обработчика для набора похожих элементов.

При присоединении функции-обработчика к элементу с помощью addEventListener(), значение this внутри обработчика будет ссылкой на элемент. Оно будет таким же, как значение свойства currentTarget аргумента события, переданного обработчику.

my_element.addEventListener("click", function (e) {
  console.log(this.className); // logs the className of my_element
  console.log(e.currentTarget === this); // logs `true`
});

Напоминаем, что стрелочные функции не имеют собственного контекста this.

my_element.addEventListener("click", (e) => {
  console.log(this.className); // WARNING: `this` is not `my_element`
  console.log(e.currentTarget === this); // logs `false`
});

Если обработчик события (например, onclick) указан в исходном HTML-коде элемента, JavaScript-код в значении атрибута фактически обернут в функцию-обработчик, которая связывает значение this таким образом, как и addEventListener(); вхождение this в коде представляет ссылку на элемент.

<table id="my_table" onclick="console.log(this.id);">
  <!-- `this` refers to the table; logs 'my_table' -->
  …
</table>

Обратите внимание, что значение this внутри функции, вызываемой кодом в атрибуте значения, ведет себя в соответствии со стандартными правилами. Это показано в следующем примере:

<script>
  function logID() {
    console.log(this.id);
  }
</script>
<table id="my_table" onclick="logID();">
  <!-- when called, `this` will refer to the global object -->
  …
</table>

Значение this внутри logID() — ссылка на глобальный объект Window (или undefined в случае строгого режима.

Указание "this" с помощью bind()

Метод Function.prototype.bind() позволяет установить фиксированный контекст this для всех последующих вызовов — минуя проблемы, когда неясно, что будет this, в зависимости от контекста, из которого была вызвана ваша функция. Обратите внимание, однако, что вам нужно сохранить ссылку на слушатель, чтобы вы могли его удалить позже.

Это пример с bind() и без него:

class Something {
  name = "Something Good";
  constructor(element) {
    // bind causes a fixed `this` context to be assigned to `onclick2`
    this.onclick2 = this.onclick2.bind(this);
    element.addEventListener("click", this.onclick1, false);
    element.addEventListener("click", this.onclick2, false); // Trick
  }
  onclick1(event) {
    console.log(this.name); // undefined, as `this` is the element
  }
  onclick2(event) {
    console.log(this.name); // 'Something Good', as `this` is bound to the Something instance
  }
}

const s = new Something(document.body);

Другое решение — использование специальной функции, называемой handleEvent() для захвата событий:

class Something {
  name = "Something Good";
  constructor(element) {
    // Note that the listeners in this case are `this`, not this.handleEvent
    element.addEventListener("click", this, false);
    element.addEventListener("dblclick", this, false);
  }
  handleEvent(event) {
    console.log(this.name); // 'Something Good', as this is bound to newly created object
    switch (event.type) {
      case "click":
        // some code here…
        break;
      case "dblclick":
        // some code here…
        break;
    }
  }
}

const s = new Something(document.body);

Ещё один способ обработки ссылки на this — использование стрелочной функции, которая не создаёт отдельный контекст this.

class SomeClass {
  name = "Something Good";

  register() {
    window.addEventListener("keydown", (e) => {
      this.someMethod(e);
    });
  }

  someMethod(e) {
    console.log(this.name);
    switch (e.code) {
      case "ArrowUp":
        // some code here…
        break;
      case "ArrowDown":
        // some code here…
        break;
    }
  }
}

const myObject = new SomeClass();
myObject.register();

Получение данных в обработчик события и из него

Обработчики событий принимают только один аргумент, Event или подкласс Event, который автоматически передаётся слушателю, и возвращаемое значение игнорируется. Поэтому для получения данных в обработчик события и из него, вместо передачи данных через параметры и возвращаемые значения, необходимо создавать замыкания вместо этого.

Функции, передаваемые в качестве обработчиков событий, имеют доступ ко всем переменным, объявленным во внешних областях видимости, содержащих функцию.

const myButton = document.getElementById("my-button-id");
let someString = "Data";

myButton.addEventListener("click", () => {
  console.log(someString);
  // 'Data' on first click,
  // 'Data Again' on second click

  someString = "Data Again";
});

console.log(someString); // Expected Value: 'Data' (will never output 'Data Again')

Подробнее о областях видимости функций см. в руководстве по функциям.

Проблемы с памятью

const elts = document.getElementsByTagName("*");

// Case 1
for (const elt of elts) {
  elt.addEventListener(
    "click",
    (e) => {
      // Do something
    },
    false,
  );
}

// Case 2
function processEvent(e) {
  // Do something
}

for (const elt of elts) {
  elt.addEventListener("click", processEvent, false);
}

В первом случае выше, с каждым итерационным циклом создаётся новая (анонимная) функция-обработчик. Во втором случае используется та же ранее объявленная функция в качестве обработчика событий, что приводит к меньшему потреблению памяти, так как создаётся только одна функция-обработчик. Более того, в первом случае невозможно вызвать removeEventListener(), поскольку ссылка на анонимную функцию не сохраняется (или здесь не сохраняется ни одна из множественных анонимных функций, которые цикл может создать). Во втором случае это возможно, так как myElement.removeEventListener("click", processEvent, false) — ссылка на функцию.

На самом деле, по поводу потребления памяти, отсутствие сохранения ссылки на функцию не является основной проблемой; скорее это отсутствие сохранения статической ссылки на функцию.

Использование пассивных слушателей

Если событие имеет действие по умолчанию — например, событие wheel, которое по умолчанию прокручивает контейнер — браузер, как правило, не может начать действие по умолчанию до тех пор, пока обработчик события не завершит работу, поскольку он заранее не знает, может ли обработчик события отменить действие по умолчанию, вызвав Event.preventDefault(). Если обработчик события выполняется слишком долго, это может привести к заметной задержке, также известной как заикание, перед тем как можно будет выполнить действие по умолчанию.

Установив параметр passive в значение true, обработчик события заявляет, что он не будет отменять действие по умолчанию, поэтому браузер может сразу начать действие по умолчанию, не дожидаясь завершения выполнения слушателя. Если слушатель затем вызовет Event.preventDefault(), это не повлияет.

Спецификация для addEventListener() определяет значение по умолчанию для параметра passive как всегда false. Однако, чтобы реализовать преимущества пассивных слушателей для производительности прокрутки в устаревшем коде, современные браузеры изменили значение параметра passive на true для событий wheel, mousewheel, touchstart и touchmove на узлах уровня документа Window, Document и Document.body. Это предотвращает обработчик событий от отмены события, так что он не может блокировать рендеринг страницы во время прокрутки пользователем.

Поэтому, если вы хотите переопределить это поведение и убедиться, что параметр passive имеет значение false, вы должны явно установить этот параметр в значение false (а не полагаться на значение по умолчанию).

Для базового события scroll вам не нужно беспокоиться о значении passive. Поскольку оно не может быть отменено, обработчики событий не могут блокировать рендеринг страницы в любом случае.

См. Улучшение производительности прокрутки с помощью пассивных слушателей для примера, демонстрирующего эффект пассивных слушателей.

Примеры

Добавление простого слушателя

Этот пример демонстрирует, как использовать addEventListener() для отслеживания щелчков мышью по элементу.

HTML

<table id="outside">
  <tr>
    <td id="t1">one</td>
  </tr>
  <tr>
    <td id="t2">two</td>
  </tr>
</table>

JavaScript

// Function to change the content of t2
function modifyText() {
  const t2 = document.getElementById("t2");
  const isNodeThree = t2.firstChild.nodeValue === "three";
  t2.firstChild.nodeValue = isNodeThree ? "two" : "three";
}

// Add event listener to table
const el = document.getElementById("outside");
el.addEventListener("click", modifyText, false);

В данном коде modifyText() — это слушатель для click событий, зарегистрированных с помощью addEventListener(). Щелчок в любой части таблицы поднимается до обработчика и выполняет modifyText().

Результат

Добавление прерывимого слушателя

В этом примере показано, как добавить addEventListener(), который можно прервать с помощью AbortSignal.

HTML

<table id="outside">
  <tr>
    <td id="t1">one</td>
  </tr>
  <tr>
    <td id="t2">two</td>
  </tr>
</table>

JavaScript

// Add an abortable event listener to table
const controller = new AbortController();
const el = document.getElementById("outside");
el.addEventListener("click", modifyText, { signal: controller.signal });

// Function to change the content of t2
function modifyText() {
  const t2 = document.getElementById("t2");
  if (t2.firstChild.nodeValue === "three") {
    t2.firstChild.nodeValue = "two";
  } else {
    t2.firstChild.nodeValue = "three";
    controller.abort(); // remove listener after value reaches "three"
  }
}

В приведённом примере мы модифицируем код из предыдущего примера таким образом, что после изменения содержимого второй строки на "three", мы вызываем abort() из AbortController, переданного в вызов addEventListener(). В результате значение остаётся "three" навсегда, так как у нас больше нет кода, слушающего событие щелчка.

Результат

Обработчик событий с анонимной функцией

Здесь мы рассмотрим, как использовать анонимную функцию для передачи параметров в обработчик событий.

HTML

<table id="outside">
  <tr>
    <td id="t1">one</td>
  </tr>
  <tr>
    <td id="t2">two</td>
  </tr>
</table>

JavaScript

// Function to change the content of t2
function modifyText(new_text) {
  const t2 = document.getElementById("t2");
  t2.firstChild.nodeValue = new_text;
}

// Function to add event listener to table
const el = document.getElementById("outside");
el.addEventListener(
  "click",
  function () {
    modifyText("four");
  },
  false,
);

Обратите внимание, что слушатель — это анонимная функция, которая encapsulates код, который затем, в свою очередь, может передавать параметры функции modifyText(), которая отвечает за фактическое реагирование на событие.

Результат

Обработчик событий со стрелочной функцией

Этот пример демонстрирует обработчик событий, реализованный с использованием стрелочной функции.

HTML

<table id="outside">
  <tr>
    <td id="t1">one</td>
  </tr>
  <tr>
    <td id="t2">two</td>
  </tr>
</table>

JavaScript

// Function to change the content of t2
function modifyText(new_text) {
  const t2 = document.getElementById("t2");
  t2.firstChild.nodeValue = new_text;
}

// Add event listener to table with an arrow function
const el = document.getElementById("outside");
el.addEventListener(
  "click",
  () => {
    modifyText("four");
  },
  false,
);

Результат

Обратите внимание, что, хотя анонимные и стрелочные функции похожи, у них разные this связи. В то время как анонимные (и все традиционные функции JavaScript) создают свои собственные this связи, стрелочные функции наследуют this связь содержащей функции.

Это означает, что переменные и константы, доступные содержащей функции, также доступны обработчику событий при использовании стрелочной функции.

Пример использования опций

HTML

<div class="outer">
  outer, once & none-once
  <div class="middle" target="_blank">
    middle, capture & none-capture
    <a class="inner1" href="https://www.mozilla.org" target="_blank">
      inner1, passive & preventDefault(which is not allowed)
    </a>
    <a class="inner2" href="https://developer.mozilla.org/" target="_blank">
      inner2, none-passive & preventDefault(not open new page)
    </a>
  </div>
</div>
<hr />
<button class="clear-button">Clear logs</button>
<section class="demo-logs"></section>

CSS

.outer,
.middle,
.inner1,
.inner2 {
  display: block;
  width: 520px;
  padding: 15px;
  margin: 15px;
  text-decoration: none;
}
.outer {
  border: 1px solid red;
  color: red;
}
.middle {
  border: 1px solid green;
  color: green;
  width: 460px;
}
.inner1,
.inner2 {
  border: 1px solid purple;
  color: purple;
  width: 400px;
}

JavaScript

const outer = document.querySelector(".outer");
const middle = document.querySelector(".middle");
const inner1 = document.querySelector(".inner1");
const inner2 = document.querySelector(".inner2");

const capture = {
  capture: true,
};
const noneCapture = {
  capture: false,
};
const once = {
  once: true,
};
const noneOnce = {
  once: false,
};
const passive = {
  passive: true,
};
const nonePassive = {
  passive: false,
};

outer.addEventListener("click", onceHandler, once);
outer.addEventListener("click", noneOnceHandler, noneOnce);
middle.addEventListener("click", captureHandler, capture);
middle.addEventListener("click", noneCaptureHandler, noneCapture);
inner1.addEventListener("click", passiveHandler, passive);
inner2.addEventListener("click", nonePassiveHandler, nonePassive);

function onceHandler(event) {
  log("outer, once");
}
function noneOnceHandler(event) {
  log("outer, none-once, default\n");
}
function captureHandler(event) {
  //event.stopImmediatePropagation();
  log("middle, capture");
}
function noneCaptureHandler(event) {
  log("middle, none-capture, default");
}
function passiveHandler(event) {
  // Unable to preventDefault inside passive event listener invocation.
  event.preventDefault();
  log("inner1, passive, open new page");
}
function nonePassiveHandler(event) {
  event.preventDefault();
  //event.stopPropagation();
  log("inner2, none-passive, default, not open new page");
}

Результат

Щёлкните по внешнему, среднему и внутреннему контейнерам соответственно, чтобы увидеть, как работают опции.

Обработчик событий с несколькими опциями

Вы можете установить более одной опции в параметре options. В следующем примере мы устанавливаем две опции:

  • passive, чтобы убедиться, что обработчик не вызовет preventDefault()
  • once, чтобы гарантировать, что обработчик событий будет вызван только один раз.

HTML

<button id="example-button">You have not clicked this button.</button>
<button id="reset-button">Click this button to reset the first button.</button>

JavaScript

const buttonToBeClicked = document.getElementById("example-button");

const resetButton = document.getElementById("reset-button");

// the text that the button is initialized with
const initialText = buttonToBeClicked.textContent;

// the text that the button contains after being clicked
const clickedText = "You have clicked this button.";

// we hoist the event listener callback function
// to prevent having duplicate listeners attached
function eventListener() {
  buttonToBeClicked.textContent = clickedText;
}

function addListener() {
  buttonToBeClicked.addEventListener("click", eventListener, {
    passive: true,
    once: true,
  });
}

// when the reset button is clicked, the example button is reset,
// and allowed to have its state updated again
resetButton.addEventListener("click", () => {
  buttonToBeClicked.textContent = initialText;
  addListener();
});

addListener();

Результат

Улучшение производительности прокрутки с помощью пассивных обработчиков

Следующий пример демонстрирует эффект установки passive. Он включает в себя <div>, содержащий текст, и флажок.

HTML

<div id="container">
  <p>
    But down there it would be dark now, and not the lovely lighted aquarium she
    imagined it to be during the daylight hours, eddying with schools of tiny,
    delicate animals floating and dancing slowly to their own serene currents
    and creating the look of a living painting. That was wrong, in any case. The
    ocean was different from an aquarium, which was an artificial environment.
    The ocean was a world. And a world is not art. Dorothy thought about the
    living things that moved in that world: large, ruthless and hungry. Like us
    up here.
  </p>
</div>

<div>
  <input type="checkbox" id="passive" name="passive" checked />
  <label for="passive">passive</label>
</div>

JavaScript

Код добавляет обработчик к событию wheel контейнера, которое по умолчанию прокручивает контейнер. Обработчик выполняет длительную операцию. Изначально обработчик добавляется с опцией passive, и всякий раз, когда флажок переключается, код переключает опцию passive.

const passive = document.querySelector("#passive");
passive.addEventListener("change", (event) => {
  container.removeEventListener("wheel", wheelHandler);
  container.addEventListener("wheel", wheelHandler, {
    passive: passive.checked,
    once: true,
  });
});

const container = document.querySelector("#container");
container.addEventListener("wheel", wheelHandler, {
  passive: true,
  once: true,
});

function wheelHandler() {
  function isPrime(n) {
    for (let c = 2; c <= Math.sqrt(n); ++c) {
      if (n % c === 0) {
        return false;
      }
    }
    return true;
  }

  const quota = 1000000;
  const primes = [];
  const maximum = 1000000;

  while (primes.length < quota) {
    const candidate = Math.floor(Math.random() * (maximum + 1));
    if (isPrime(candidate)) {
      primes.push(candidate);
    }
  }

  console.log(primes);
}

Результат

Эффект заключается в следующем:

  • Изначально обработчик пассивный, поэтому попытка прокрутить контейнер колесиком происходит мгновенно.
  • Если вы снимите флажок «пассивный» и попытаетесь прокрутить контейнер колесиком, то будет заметная задержка, прежде чем контейнер прокрутится, так как браузер должен дождаться завершения длительного выполнения обработчика.

Спецификации

Спецификация
DOM
# ref-for-dom-eventtarget-addeventlistener③

Совместимость с браузерами

Рабочий стол Мобильные устройства
Chrome Edge Firefox Opera Safari Chrome Android Firefox для Android Opera Android Safari в IOS Samsung Internet WebView Android
addEventListener
1До Chrome 49, параметры type и listener были необязательными.
12 1 7 1
18До Chrome Android 49, параметры type и listener были необязательными.
4 10.1 1
1.0До Samsung Internet 5.0, параметры type и listener были необязательными.
1До Chrome 49, параметры type и listener были необязательными.
options_parameter 49 ≤18 49 36 10 49 49 36 10 5.0 49
useCapture_parameter_optional 1 12 6 11.6 1 18 6 12 1 1.0 4.4

См. также

  • EventTarget.removeEventListener()
  • Создание и вызов пользовательских событий
  • Дополнительная информация об использовании this в обработчиках событий

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API