Делегирование jQuery .on() с селектором, соответствующим вложенным элементам

// Example A
$("#delegate").on("click", function(event) {
  // executes on click on any descendant of #delegate or self (not a delegate at all)
  // 'this' is #delegate
});


// Example B
$("#delegate").on("click", "#outer", function(event) {
  // executes on click on any descendant of #outer or self
  // 'this' is #outer or #inner (depends on the actual click)
});

$("#delegate").on("click", "#inner", function(event) {
  // executes on click on any descendant of #inner or self (nothing happens when clicking #outer)
  // 'this' is #inner
});


// Example C (now it's getting weird)
$("#delegate").on("click", "div", function(event) {
  // executes twice, when clicking #inner, because the event passes #outer when bubbling up

  // one time 'this' is #inner, and the other time 'this' is #outer
  // stopPropagation() // actually prevents the second execution
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="delegate">
  <div id="outer">
    outer
    <div id="inner">
      inner
    </div>
  </div>
</div>

Как логически объяснить такое поведение?

Существует ровно одно событие клика, которое начинается #inner, всплывает через #outer и, наконец, достигает #delegate.

Событие перехватывается (точно) один раз обработчиком #delegate. Обработчик проверяет, содержит ли история события какие-либо элементы div.

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

Это становится еще более сумасшедшим, если вы посмотрите на поведение stopPropagation(). На самом деле вы можете избежать второго выполнения, хотя событие уже достигло #delegate. stopPropagation(здесь не должно работать.

Какая «магия» делается при реализации логики делегирования? Разделяются ли каким-либо образом всплытие событий и программный поток?

Пожалуйста, не публикуйте «практические советы» в первую очередь («Используйте вместо этого xyz!»). Я хотел бы понять, почему код работает именно так.


person TheBlindMinstrel    schedule 11.09.2015    source источник
comment
Вы пропустили событие click во втором и третьем вызове.   -  person Barmar    schedule 11.09.2015
comment
Вы спрашиваете только о последнем случае с делегированием div?   -  person Barmar    schedule 11.09.2015
comment
Спасибо. Я только что добавил события кликов в свой пост. И да: в основном я просто спрашиваю о последнем случае.   -  person TheBlindMinstrel    schedule 11.09.2015
comment
какой у Вас вопрос? Как логически объяснить такое поведение? ну, есть два <div> в #delegate, и присоединение события click ко всем div внутри него, следовательно, вызовет 2 события при нажатии #inner. Конечно, использование stopPropagation предотвратит всплытие с #inner на #outer.   -  person Alex    schedule 11.09.2015
comment
Таким образом, событие не поднимается до #delegate, а ТОГДА выполняет обратный вызов? Вместо этого обработчики/обратные вызовы на самом деле привязаны к конкретным элементам div?   -  person TheBlindMinstrel    schedule 11.09.2015
comment
см. здесь: jsfiddle.net/58kq61o9/2   -  person Alex    schedule 11.09.2015
comment
см. здесь только пример C: jsfiddle.net/58kq61o9/4   -  person Alex    schedule 11.09.2015


Ответы (3)


Поскольку вы связываете события со всеми div двумя способами:

  1. Использование атрибута id элемента div.
  2. Используя имя тега элемента div.

поэтому, если вы event.stopPropagation(); на последнем, все равно предупреждение придет два раза, потому что вы щелкнули div, а также #inner (например.)

Проверьте фрагмент ниже.

$("#delegate").on("click", function(event) {
  alert(this.id);
});

/*
$("#delegate").on('click', "#outer", function(event) {
  alert(this.id);
});

$("#delegate").on('click', "#inner", function(event) {
  alert(this.id);
});

*/
// now it's getting weird
$("#delegate").on("click", "div", function(event) {
  event.stopPropagation();
  alert(this.id);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="delegate">
  <div id="outer">
    outer
    <div id="inner">
      inner
    </div>
  </div>
</div>

person Jai    schedule 11.09.2015
comment
Извините, я забыл сказать, что все разные .on()-вызовы в моем фрагменте являются разными примерами и не устанавливаются одновременно. теперь это становится странным - call - это единственное, что вам действительно нужно. - person TheBlindMinstrel; 11.09.2015

Способ делегирования событий заключается в том, что jQuery проходит вверх по дереву DOM от самой внутренней цели к элементу, к которому было привязано делегирование, проверяя каждый элемент, чтобы увидеть, соответствует ли он селектору. Если это так, он выполняет обработчик с this, привязанным к этому элементу.

Когда вызывается event.stopPropagation, он устанавливает флаг в объекте event. Цикл, который проходит по дереву DOM, также вызывает event.isPropagationStopped(). Если распространение остановлено, оно выходит из цикла.

Другими словами, jQuery выполняет свое собственное всплытие и останавливает распространение, когда он реализует делегирование, он не использует всплывание браузера (за исключением того, что это всплывание необходимо для запуска начального события в #delegate, так что цикл jQuery будет выполняться ).

person Barmar    schedule 11.09.2015
comment
Хм ... кажется, понимание jQuery делает свое собственное пузырение, кроме / до того, как пузырение браузеров было подсказкой, которую я упустил. Спасибо. ;) - person TheBlindMinstrel; 11.09.2015
comment
jQuery не добавляет собственного всплытия. Он добавляет всплывающую подсказку для браузеров, не поддерживающих его, например, IE8 для события изменения. - person Alex; 11.09.2015
comment
Наверное, я неправильно понял эту часть кода. Но он по-прежнему выполняет собственную проверку stopPropagation. - person Barmar; 11.09.2015

Все работает так, как ожидалось. Взгляните на эту скрипту:

$("#delegate").on("click", "div", function(event) {
  // event.stopPropagation();
  alert('div: ' + $(this).attr('id'));
});

Нажатие #inner вызовет указанное выше событие. Событие будет всплывать до #outer, так как #inner является потомком #outer. Поскольку #outer также является <div, событие также будет запущено на #outer. Логично, что нажатие на #inner сначала выдаст предупреждение "div: inner", а затем "div: external".

Вызов event.stopPropagation() говорит, что событие не всплывает, поэтому #outer остается невызванным.

Кроме этой скрипки:

<div id="delegate">
  <div id="first">
    first
  </div>
  <div id="second">
    second
      <div id="third">
        third, inside second
      </div>
    </div>
</div>

Нажатие на третье сначала предупредит third, затем second, а затем остановит, потому что #first не родитель, а родной брат.

person Alex    schedule 11.09.2015