Подробное объяснение делегирования событий JavaScript

JavaScript
Подробное объяснение делегирования событий JavaScript

основная концепция

Делегирование события, с точки зрения непрофессионала, заключается в делегировании функции элемента, отвечающего на событие (щелчок, нажатие клавиши...) другому элементу;

Вообще говоря, событие одного или группы элементов делегируется его родительскому или внешнему элементу. Внешний элемент — это тот, который фактически связывает событие. Когда событие отвечает на элемент, который необходимо связать, оно передает Таким образом, механизм всплытия событий инициирует событие привязки своего внешнего элемента, а затем выполняет функцию на внешнем элементе.

Например, если одноклассник в общежитие приходит одновременно, то один путь - все по одному тупо идут его забирать, а другой способ - поручить это дело заведующему общежитием, и пусть выходит один человек получить всех курьеров, Затем раздать каждому студенту общежития по одному в зависимости от получателя;

Здесь забрать курьера — это событие, каждый одноклассник ссылается на элемент ДОМ, который должен отреагировать на событие, а начальник общежития, выходящий забрать курьера, — это элемент агента, поэтому именно этот элемент действительно привязывает событие, которое распределяется по получателю.Процесс экспресс-доставки заключается в том, что при выполнении события необходимо определить, какой из проксируемых элементов должен соответствовать в данный момент отвечающему событию.

всплывающее окно события

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

В document.addEventListener мы можем задать модель события: всплытие событий, захват событий, вообще говоря, мы используем модели всплытия событий;

Как показано на рисунке выше, событийная модель разделена на три этапа:

  • Фаза захвата: в модели пузырькового распространения событий фаза захвата не реагирует ни на какие события;
  • Целевой этап: Целевой этап означает, что событие реагирует на самый нижний элемент, который запускает событие;
  • Стадия всплытия: Этап всплытия заключается в том, что триггерный ответ события будет выходить из нижней цели на самый внешний уровень (корневой узел) слой за слоем.Привязать к внешнему слою; ### события

Преимущества делегирования

1. Уменьшить потребление памяти

Представьте, если у нас есть список с большим количеством элементов списка, нам нужно реагировать на событие при нажатии на элемент списка;

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

Если вы привяжете функцию к каждому элементу списка, она будет потреблять много памяти и сильно снижать производительность;

Следовательно, лучший способ — привязать событие клика к его родительскому слою, то есть `ul`, а затем сопоставить и оценить целевой элемент при выполнении события;

Таким образом, делегирование событий может существенно сократить потребление памяти и повысить эффективность.

2. Динамически связывать события

Например, в приведенном выше примере всего несколько элементов списка, и мы привязываем события к каждому элементу списка;

Во многих случаях нам нужно динамически добавлять или удалять элементы элемента списка с помощью AJAX или пользовательских операций, тогда каждый раз, когда мы вносим изменения, нам нужно повторно привязывать события для вновь добавленных элементов и отменять привязку событий для элементов, которые собираются удалить. ;

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

Таким образом, использование событий может сократить объем повторяющейся работы в случае динамического связывания событий.

Делегирование событий в jQuery

Считается, что делегация мероприятия в JQuery используется многими людьми, и в основном это реализуется в эти пути:
  • $.on: Основное использование: $('.parent').on('click', 'a', function () { console.log('событие click на теге a'); }), которое находится под элементом .parent. событие элемента a делегируется $('.parent'), пока есть событие щелчка по этому элементу, он автоматически найдет элемент a под элементом .parent, а затем отреагирует на событие;
  • $.delegate: Основное использование: $('.parent').delegate('a', 'click', function () { console.log('событие click на теге a'); }), то же, что и выше, и соответствующий $. делегировать для удаления делегированных событий;
  • $.live: Основное использование: $('a', $('.parent')).live('click', function () { console.log('событие click на теге a'); }), то же, что и выше, но Если родительский элемент $(.parent) не передан, событие будет делегировано $(document) по умолчанию (устарело)

реализовать функцию

Базовая реализация

Например, у нас есть такой HTML-фрагмент:
<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li


Давайте реализуем делегирование события элемента li в #list его родительскому элементу, которым является #list:

// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
  // 兼容性处理
  var event = e || window.event;
  var target = event.target || event.srcElement;
  // 判断是否匹配目标元素
  if (target.nodeName.toLocaleLowerCase === 'li') {
    console.log('the content is: ', target.innerHTML);
  }
});


В приведенном выше коде целевой элемент — это элемент, который специально щелкнули под элементом #list, а затем, оценивая некоторые атрибуты цели (такие как: nodeName, id и т. д.), его можно более точно сопоставить с элементом. определенный тип элемента #list li выше;

Точное совпадение с использованием Element.matches

Если вы измените HTML на:
<ul id="list">
  <li className="class-1">item 1</li>
  <li>item 2</li>
  <li className="class-1">item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

Здесь мы хотим делегировать событие щелчка элемента li (и его класс — class-1) под элементом #list в #list;

Если мы используем вышеуказанный метод, нам также необходимо добавить суждение `target.nodeName.className === 'class-1'` в суждение `if (target.nodeName.toLocaleLowerCase === 'li')`;

Но если вы хотите сделать более гибкое сопоставление, как это выбирает CSS, приведенных выше суждений слишком много, и трудно добиться гибкости, вы можете использовать API Element.matches для сопоставления;

Основное использование Element.matches API: Element.matches(selectorString), selectorString — это правило селектора, такое как CSS, например, в этом примере можно использовать target.matches('li.class-1'), он вернет логическое значение value , если целевой элемент — это тег li и его класс — class-1, то он вернет true, иначе — false;

Конечно, все еще есть некоторые проблемы с его совместимостью, для чего требуются современные версии браузеров IE9 и выше;

мы можем использоватьPolyfillЧтобы решить проблемы совместимости:

if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.matchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector ||
    Element.prototype.oMatchesSelector ||
    Element.prototype.webkitMatchesSelector ||
    function(s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s),
        i = matches.length;
      while (--i >= 0 && matches.item(i) !== this) {}
      return i > -1;            
    };
}

После добавления Element.matches мы можем достичь наших целей:

if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.matchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector ||
    Element.prototype.oMatchesSelector ||
    Element.prototype.webkitMatchesSelector ||
    function(s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s),
        i = matches.length;
      while (--i >= 0 && matches.item(i) !== this) {}
      return i > -1;            
    };
}
document.getElementById('list').addEventListener('click', function (e) {
  // 兼容性处理
  var event = e || window.event;
  var target = event.target || event.srcElement;
  if (target.matches('li.class-1')) {
    console.log('the content is: ', target.innerHTML);
  }
});

инкапсуляция функций

При работе с большим количеством сценариев мы можем инкапсулировать функцию прокси события в общедоступную функцию, чтобы ее можно было использовать повторно.
Объедините приведенный выше пример, чтобы реализовать функцию eventDelegate, которая принимает четыре параметра:
  • [String] Строка селектора используется для фильтрации родительского элемента, который должен реализовать прокси, то есть событие должно быть фактически привязано;
  • [String] Строка селектора, используемая для фильтрации потомков элемента селектора, запускающего событие, то есть элемента, которому нужно делегировать события;
  • [String] Один или несколько разделенных пробелом типов событий и необязательных пространств имен, таких как click или keydown.click ;
  • [Функция] Функция, которой требуется ответ на прокси-событие;

Вот несколько ключевых моментов:
  • Может быть несколько элементов родительского прокси, и события должны быть связаны одно за другим;
  • Может быть несколько типов событий, которые нужно связать, и события должны быть связаны одно за другим;
  • Среди агентов обработки соответствующие элементы должны рассмотреть вопрос о совместимости;
  • При выполнении связанной функции вам необходимо передать правильные параметры и принять это во внимание;
function eventDelegate (parentSelector, targetSelector, events, foo) {
  // 触发执行的函数
  function triFunction (e) {
    // 兼容性处理
    var event = e || window.event;
    var target = event.target || event.srcElement;
    // 处理 matches 的兼容性
    if (!Element.prototype.matches) {
      Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
          var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
          while (--i >= 0 && matches.item(i) !== this) {}
          return i > -1;            
        };
    }
    // 判断是否匹配到我们所需要的元素上
    if (target.matches(targetSelector)) {
      // 执行绑定的函数,注意 this
      foo.call(target, Array.prototype.slice.call(arguments));
    }
  }
  // 如果有多个事件的话需要全部一一绑定事件
  events.split('.').forEach(function (evt) {
    // 多个父层元素的话也需要一一绑定
    Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(function ($p) {
      $p.addEventListener(evt, triFunction);
    });
  });
}

?оптимизация

Когда проксируемый элемент не является целевым элементом, то есть элемент, на который указывает селектор targetSelector, не является event.target (элемент, на который указывает этап целевого события), тогда необходимо пройти через parentNode для event.target слой за слоем, чтобы соответствовать targetSelector. , пока parentSelector.

Например:

<ul id="list">
  <li><span>item 1</span></li>
  <li><span>item 2</span></li>
</ul>

Или проксируйте событие li в #list, но в это время вы обнаружите, что event.target указывает на li span, поэтому вам нужно пройтись по внешним элементам для соответствия, пока вы не достигнете функции проксирования события, мы можем использовать event.currentTarget Чтобы получить функцию прокси-события;

Полная функция:

function eventDelegate (parentSelector, targetSelector, events, foo) {
  // 触发执行的函数
  function triFunction (e) {
    // 兼容性处理
    var event = e || window.event;

    // 获取到目标阶段指向的元素
    var target = event.target || event.srcElement;

    // 获取到代理事件的函数
    var currentTarget = event.currentTarget;

    // 处理 matches 的兼容性
    if (!Element.prototype.matches) {
      Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
          var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
          while (--i >= 0 && matches.item(i) !== this) {}
          return i > -1;            
        };
    }

    // 遍历外层并且匹配
    while (target !== currentTarget) {
      // 判断是否匹配到我们所需要的元素上
      if (target.matches(targetSelector)) {
        var sTarget = target;
        // 执行绑定的函数,注意 this
        foo.call(sTarget, Array.prototype.slice.call(arguments))
      }

      target = target.parentNode;
    }
  }

  // 如果有多个事件的话需要全部一一绑定事件
  events.split('.').forEach(function (evt) {
    // 多个父层元素的话也需要一一绑定
    Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(function ($p) {
      $p.addEventListener(evt, triFunction);
    });
  });
}

Используйте функцию:

eventDelegate('#list', 'li', 'click', function () { console.log(this); });

После нажатия вы увидите, что консоль показывает объект элемента `#list li`;

? ограничение

Конечно, делегирование событий также имеет определенные ограничения;

Такие события, как фокус и размытие, не имеют механизма всплытия событий, поэтому их нельзя делегировать;

Такие события, как mousemove и mouseout, хотя и имеют всплывающее окно событий, могут непрерывно вычислять позиционирование только через позицию, что требует высокой производительности, поэтому не подходит для делегирования событий;

???Также приглашаем подписаться на мой блог, вас ждут новые статьи

qiutc.me/