Front-end talk: принцип события DOM

внешний интерфейс JavaScript браузер HTML

DOM-события — это то, к чему привыкли фронтенд-разработчики.Мониторинг событий и триггеры очень удобны в использовании, но каков их принцип?Как браузер обрабатывает событиясвязыватьа такжевызыватьчто о?

Давайте реализуем простуюобработчик события, чтобы узнать больше об этом.

Во-первых, как зарегистрировать событие?

По сравнению с этим всем очень ясно, естьтриспособ зарегистрироваться:

  1. Регистрация тегов HTML
<button onclick="alert('hello!');">Say Hello!</button>
  1. для DOM-узловonXXXуступка имущества
document.getElementById('elementId').onclick = function() {
  console.log('I clicked it!')
}
  1. использоватьaddEventListener()Регистрация событий (преимущество в том, что вы можете зарегистрировать несколько обработчиков событий)
document.getElementById('elementId').addEventListener(
  'click',
  function() {
    console.log('I clicked it!')
  },
  false
)

Как события передаются между узлами DOM?

Проще говоря: доставка мероприятияСначала сверху вниз, затем снизу вверх

В полном объеме: доставка мероприятия делится на два этапа:этап захватаа такжепузырьковая стадия

Давайте рассмотрим конкретный пример:

<html>
  <head> </head>

  <body>
    <div id="parentDiv">
      <a id="childButton" href="https://github.com"> click me! </a>
    </div>
  </body>
</html>

Когда мы щелкаем тег a в html-коде выше, браузер сначала вычисляет расстояние от тега a до тега html.путь к узлу(который:html => body => div => a).

Затем перейдите к этапу захвата: активируйте регистрацию по очереди.html => body => div => aщелкните обработчик события типа захвата на .

После достижения узла а. Войдите в фазу пузыря. Начните последовательноa => div => body => htmlОбработчик события щелчка типа пузырь, зарегистрированный в .

Наконец, когда этап пузыря достигает узла html, будет запущено поведение браузера по умолчанию (для тега a в этом примере это переход на указанную веб-страницу).

На рисунке ниже мы можем увидеть процесс доставки событий более интуитивно.

image

Итак, как же реализован такой поток доставки событий?

Давайте посмотримaddEventListenerРеализация кода:

HTMLNode.prototype.addEventListener = function(eventName, handler, phase) {
  if (!this.__handlers) this.handlers = {}
  if (!this.__handlers[eventName]) {
    this.__handlers[eventName] = {
      capture: [],
      bubble: []
    }
  }
  this.__handlers[eventName][phase ? 'capture' : 'bubble'].push(handler)
}

Приведенный выше код очень интуитивно понятен, addEventListener сохранит обработчик в имени события и фазе.__handlerВ массиве обработчик захватного типа и обработчик пузырькового типа хранятся отдельно.

Далее следует основная часть этой статьи: как событие запускает обработчик?

Для простоты понимания здесь мы пытаемся реализовать простую версию функции триггера события.handler()(Это не исходный код браузера для обработки событий, но идея та же)

Сначала давайте проясним шаги процесса обработки событий браузером:

  1. Создайте объект события, инициализируйте необходимые данные
  2. Вычисляет узел DOM, который инициировал событие события для узла html.путь к узлу (DOM path)
  3. Триггерные обработчики захвата типа
  4. Запустить обработчик, привязанный к свойству onXXX
  5. запускает обработчики типа bubble
  6. Активировать поведение браузера по умолчанию для этого узла DOM.
1. Создайте объект события и инициализируйте необходимые данные
function initEvent(targetNode) {
  let ev = new Event()
  ev.target = targetNode // ev.target 是当前用户真正出发的节点
  ;(ev.isPropagationStopped = false), // 是否停止event的传播
    (ev.isDefaultPrevented = false) // 是否阻止浏览器默认的行为

  ev.stopPropagation = function() {
    this.isPropagationStopped = true
  }
  ev.preventDefault = function() {
    this.isDefaultPrevented = true
  }
  return ev
}
2. Рассчитайте расстояние от узла DOM, который запускает событие события, до узла html.путь к узлу
function calculateNodePath(event) {
  let target = event.target
  let elements = [] // 用于存储从当前节点到html节点的 节点路径
  do elements.push(target)
  while ((target = target.parentNode))
  return elements.reverse() // 节点顺序为: targetElement ==> html
}
3. Обработчики триггеров типа захвата
// 依次触发 capture类型的handlers, 顺序为: html ==> targetElement
function executeCaptureHandlers(elements, ev) {
  for (var i = 0; i < elements.length; i++) {
    if (ev.isPropagationStopped) break

    var curElement = elements[i]
    var handlers =
      (currentElement.__handlers &&
        currentElement.__handlers[ev.type] &&
        currentElement.__handlers[ev.type]['capture']) ||
      []
    ev.currentTarget = curElement
    for (var h = 0; h < handlers.length; h++) {
      handlers[h].call(currentElement, ev)
    }
  }
}
4. Запустите обработчик, привязанный к свойству onXXX.
function executeInPropertyHandler(ev) {
  if (!ev.isPropagationStopped) {
    ev.target['on' + ev.type].call(ev.target, ev)
  }
}
5. Запустите обработчики пузырькового типа
// 基本上和 capture 阶段处理方式相同
// 唯一的区别是 handlers 是逆向遍历的: targetElement ==> html

function executeBubbleHandlers(elements, ev) {
  elements.reverse()
  for (let i = 0; i < elements.length; i++) {
    if (isPropagationStopped) {
      break
    }
    var handlers =
      (currentElement.__handlers &&
        currentElement.__handlers[ev.type] &&
        currentElement.__handelrs[ev.type]['bubble']) ||
      []
    ev.currentTarget = currentElement
    for (var h = 0; h < handlers.length; h++) {
      handlers[h].call(currentElement, ev)
    }
  }
}
6. Запустите поведение браузера по умолчанию для этого узла DOM.
function executeNodeDefaultHehavior(ev) {
  if (!isDefaultPrevented) {
    // 对于 a 标签, 默认行为就是跳转链接
    if (ev.type === 'click' && ev.tagName.toLowerCase() === 'a') {
      window.location = ev.target.href
    }
    // 对于其他标签, 浏览器会有其他的默认行为
  }
}
Давайте посмотрим на полную логику вызова:
// 1.创建event对象, 初始化需要的数据
let event = initEvent(currentNode)

function handleEvent(event) {
  // 2.计算触发 event事件的DOM节点到html节点的**节点路径
  let elements = calculateNodePath(event)
  // 3.触发capture类型的handlers
  executeCaptureHandlers(elements, event)
  // 4.触发绑定在 onXXX 属性上的 handler
  executeInPropertyHandler(event)
  // 5.触发bubble类型的handlers
  executeBubbleHandlers(elements, event)
  // 6.触发该DOM节点的浏览器默认行为
  executeNodeDefaultHehavior(event)
}

Это когда пользователь отправляет событие DOM, браузерпримернопоток обработки.

propagation && defaultBehavior

Мы знаем, что это событиеstopPropagation()а такжеpreventDefault()Два метода, их роли:

stopPropagation()
  • Остановить распространение события, как видно из кода выше, вызовемstopPropagation(), последующие обработчики не будут запущены.
preventDefault()
  • Не запускайте поведение браузера по умолчанию, например:<a>Ярлыки не прыгают,<form>Форма не отправляется автоматически после нажатия кнопки «Отправить».

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

некоторые дополнения~

По умолчаниюaddEventListener()Последний параметр неверен

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

addEventListener()Реализация представляет собой нативный код.

addEventListenerЭто API, предоставляемый браузером, а не собственный API JavaScript. Когда пользователь запускает событие, браузер отправляетmessage queueДобавить задачу и выполнить эффект задачи по контуре события.

reference links:

Woohoo.bit OVI.com/blog/ А-Ц РАН…  

developer.Mozilla.org/en-US/docs/…

Хотите узнать больше о внешнем интерфейсе/D3.js/визуализации данных?

Вот адрес моего блога на github, добро пожаловать на звездочку и форк :tada:

D3-blog

Если вы считаете, что эта статья хороша, вы можете щелкнуть ссылку ниже, чтобы обратить внимание :)

домашняя страница гитхаба

Знай колонку

Наггетс

Хотите связаться со мной напрямую?

Электронная почта: ssthouse@163.com

Добро пожаловать, чтобы обратить внимание на мой общедоступный номер: