Поток событий JS и делегирование событий

JavaScript

В предыдущей статье "JS Knowledge Point Hodgepodge" мы говорили о потоке событий, но не вдавали его в подробности. В этой статье мы познакомимся с потоком событий.

Поток событий состоит из трех этапов:

1.捕获阶段
2.目标阶段
3.冒泡阶段

Как мы все знаем о привязке событий, есть уровень DOM0 (on+type) и уровень DOM2 (addEventListener). Я думаю, что сложно понять столько концепций. Просто посмотрите на код. Для удобства я буду напрямую использовать id для получить элементы.

  • Уровень DOM0
<div id="box1"></div>
box1.onclick = function(){
	console.log('box1');
}

выходbox1Мы все это знаем, давайте посмотрим.

<div id="box1"></div>
box1.onclick = function(){
	console.log('box1');
}
box1.onclick = function(){
	console.log('box1 two');
}

выходbox1 two, так как уровень DOM0 перезапишет предыдущую привязку того же элемента, давайте посмотрим.

<div id="box1">
	<div id="box2">
		<div id="box3"></div>
	</div>
</div>
box1.onclick = function(){
	console.log('box1');
}
box2.onclick = function(){
	console.log('box2');
}
box3.onclick = function(){
	console.log('box3');
}

Когда мы нажимаем на box1, мы знаем вывод box1, но что появляется, когда мы нажимаем на box3?

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

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

  • ДОМА уровень 2
<div id="box1">
	<div id="box2">
		<div id="box3"></div>
	</div>
</div>
box1.addEventListener('click', function(){
	console.log('box1');
},false);
box2.addEventListener('click', function(){
	console.log('box2');
},false);
box3.addEventListener('click', function(){
	console.log('box3');
},false);

Результат такой же, как и выше, потому что мы связаны фазой всплытия. (истинный захват, ложный пузырь). Давайте посмотрим, как выглядит этап захвата.

<div id="box1">
	<div id="box2">
		<div id="box3"></div>
	</div>
</div>
box1.addEventListener('click', function(){
	console.log('box1');
},true);
box2.addEventListener('click', function(){
	console.log('box2');
},true);
box3.addEventListener('click', function(){
	console.log('box3');
},true);

Мы нажимаем на box3, чтобы увидетьВы можете обнаружить, что порядок обратный, так почему же это так? потому чтозахват событий, что такое захват времени? Пожалуйста, обратитесь к Baidu за концепцией, прямо на карте.

Это фаза захвата, полная противоположность фазе пузыря.

Каков порядок выполнения всплытия и захвата? Я навязал одно и то же событие для обеих фаз на каждый элемент отдельно, посмотрим порядок срабатывания.

<div id="box1">
	<div id="box2">
		<div id="box3"></div>
	</div>
</div>
box1.addEventListener('click', function(){
	console.log('box1 捕获阶段');
},true);
box2.addEventListener('click', function(){
	console.log('box2 捕获阶段');
},true);
box3.addEventListener('click', function(){
	console.log('box3 捕获阶段');
},true);
box1.addEventListener('click', function(){
	console.log('box1 冒泡阶段');
},false);
box2.addEventListener('click', function(){
	console.log('box2 冒泡阶段');
},false);
box3.addEventListener('click', function(){
	console.log('box3 冒泡阶段');
},false);

Мы нажимаем на поле 3, чтобы увидеть, что оно сначала захвачено, а затем всплывает.Это все равно? Давайте немного изменим его.

box1.addEventListener('click', function(){
	console.log('box1 捕获阶段');
},true);
box2.addEventListener('click', function(){
	console.log('box2 捕获阶段');
},true);
box1.addEventListener('click', function(){
	console.log('box1 冒泡阶段');
},false);
box2.addEventListener('click', function(){
	console.log('box2 冒泡阶段');
},false);
box3.addEventListener('click', function(){
	console.log('box3 冒泡阶段');
},false);
box3.addEventListener('click', function(){
	console.log('box3 捕获阶段');
},true);  // 将box3的捕获阶段放到box3的冒泡阶段后面

Посмотрите, осталась ли последовательность запуска прежней?

Оказывается, все наоборот, на самом деле это называетсяцелевой этапБар. Захват пузыря неразличим на целевом элементе, где вы запускаете событие, в том порядке, в котором оно было привязано.

Давайте посмотрим на это с картинкой.

Это не слишком просто, давайте немного усложним.

<div id="box1">
	<div id="box2">
		<div id="box3"></div>
	</div>
</div>
box1.addEventListener('click', function(){
	console.log('box1 捕获阶段');
},true);
box2.addEventListener('click', function(){
	console.log('box2 捕获阶段');
},true);
box3.addEventListener('click', function(){
	console.log('box3 捕获阶段');
},true); 
box1.addEventListener('click', function(){
	console.log('box1 冒泡阶段');
},false);
box2.addEventListener('click', function(){
	console.log('box2 冒泡阶段');
},false);
box3.addEventListener('click', function(){
	console.log('box3 冒泡阶段');
},false);
box1.onclick = function () {
	console.log('box1 51561');
}
box2.onclick = function () {
	console.log('box2');
}
box3.onclick = function () {
	console.log('box3');
}
box1.onclick = function () {
	console.log('box1');
}

Как выглядит триггерная последовательность? (Я думаю, вам лучше сначала написать ответ самому)

посмотри, правильно ли ты понял

Это не будет слишком просто, изменить заказ

box1.onclick = function () {
	console.log('box1 51561');
}
box2.onclick = function () {
	console.log('box2');
}
box3.onclick = function () {
	console.log('box3');
}
box1.onclick = function () {
	console.log('box1');
}
box1.addEventListener('click', function(){
	console.log('box1 捕获阶段');
},true);
box2.addEventListener('click', function(){
	console.log('box2 捕获阶段');
},true);
box1.addEventListener('click', function(){
	console.log('box1 冒泡阶段');
},false);
box2.addEventListener('click', function(){
	console.log('box2 冒泡阶段');
},false);
box3.addEventListener('click', function(){
	console.log('box3 冒泡阶段');
},false);
box3.addEventListener('click', function(){
	console.log('box3 捕获阶段');
},true); 

Это правильно?

  • Поговорим о пузырении и ловле
<div id="box1">
	<div id="box2">
		<div id="box3"></div>
	</div>
</div>
box1.onclick = function () {
	console.log('box1 51561');
}
box2.onclick = function () {
	console.log('box2');
}
box3.onclick = function () {
	console.log('box3');
}
box1.onclick = function () {
	console.log('box1');
}
box1.addEventListener('click', function(){
	console.log('box1 捕获阶段');
},true);
box2.addEventListener('click', function(){
	console.log('box2 捕获阶段');
},true);
box1.addEventListener('click', function(){
	console.log('box1 冒泡阶段');
},false);
box2.addEventListener('click', function(){
	console.log('box2 冒泡阶段');
},false);
box3.addEventListener('click', function(){
	console.log('box3 冒泡阶段');
},false);
box3.addEventListener('click', function(){
	console.log('box3 捕获阶段');
},true);

Как вы думаете, что выдаст этот щелчок? Вы колеблетесь? Это значит, что вы до сих пор не понимаете бульканье и захват.

Не позволяйте тому, что вы видите, одурачить вас, пузырьковое отображение — это отношение родитель-потомок структуры DOM, а не отношение, которое выглядит как пакет. (тот же ответ, что и выше).

  • Забудьте об IE, давайте поговорим о механизме событий IE.

IE не поддерживает addEventListener, но имеет attachEvent

box1.onclick = function () {
	console.log('box1 51561');
}
box2.onclick = function () {
	console.log('box2');
}
box3.onclick = function () {
	console.log('box3');
}
box1.onclick = function () {
	console.log('box1');
}
box1.attachEvent('onclick', function (){
	console.log('box1 attachEvent')
})
box2.attachEvent('onclick', function (){
	console.log('box2 attachEvent')
})
box3.attachEvent('onclick', function (){
	console.log('box3 attachEvent')
})

box1.attachEvent('onclick', function (){
	console.log('box1')
})
box1.attachEvent('onclick', function (){
	console.log('box2')
})
box1.attachEvent('onclick', function (){
	console.log('box3')
})

Что это выведет? (подсказка: не перезапишется) Ответ: коробка1 коробка2 коробка3 Ха-ха, шучу.

Не странно ли, что событие в IE сначала связывается, а потом выводится. IE6, 7, 8 не поддерживают захват событий, поддерживают только всплывающие сообщения.

  • Совместимость с предотвращением всплытия событий

Сначала поговорим о событиях, не поддерживающих всплытие: blur, focus, mouseenter, mouseleave. (Я знаю это) Снова на этом примере давайте посмотрим на предотвращение всплытия.

box1.onclick = function (){
	console.log('box1')
}
box2.onclick = function (){
	console.log('box2')
}
box3.onclick = function (e){
	e.stopPropagation();
	console.log('box3')
}

Выводится только box3. Хотя он предотвращает всплытие, его непросто использовать в IE 8 и ниже.Давайте рассмотрим совместимый метод записи.

function stopPropagate(e){
	var event = e || window.event;
	if(event.stopPropagation){
		event.stopPropagation();
	}else if(event.cancelBubble){ //IE
		event.cancelBubble = true;
	}
}

Предотвратить совместимость событий по умолчанию

function preventDef(e){
	var g = e || window.event;
	if(g.preventDefault){
		g.preventDefault();
	}else if(g.returnValue){
		g.returnValue = false;
	}
	return false;
}

Давайте посмотрим на указатель this привязки события.

box1.onclick = function (){
	console.log('onclick', this);
}
box1.addEventListener('click',function () {
	console.log('addEventListener', this);
}, false)

IE6, 7, 8, привязка события this указывает на

box1.attachEvent('onclick',function () {
	console.log('attachEvent', this);
})

attachEvent [object Window]

Мы обнаружили, что в IE6, 7, 8 this указывает на window

  • Расширенное делегирование мероприятия
<ul id="ul">
	<li>1</li>
	<li>2</li>
	<li>3</li>
</ul>

Если бы нам нужно было следить за поведением каждого ли, вы бы сделали это?

let li = document.getElementsByTagName("li");
for (var i = 0; i < li.length; i++) {
	li.onclick = ()=>{
		console.log(i);
	}
}

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

let li = document.getElementsByTagName("li");
//ES6 
for (let i = 0; i < li.length; i++) {
	li[i].onclick = ()=>{
		console.log(i);
	}
}
//IIFE(1)
for (var i = 0; i < li.length; i++) {
	(function(j){
		li[j].onclick = ()=>{
		console.log(j);
	}})(i)
}
//IIFE(2)
for (var i = 0; i < li.length; i++) {
	li[i].onclick = (function(j){
		return ()=>{
			console.log(j);
		}
	})(i);
}

Делать это правильно недостаточно.Если вы добавите еще несколько ли или у вас будет много 100|1000 ли, вам будет плохо, если вы все еще будете это делать. Нам нужно зарегистрировать событие для каждого li, не говоря уже о том, что регистрировать много событий нехорошо. Итак, что мы делаем, используяделегация мероприятия, состоит в том, чтобы делегировать ваши события другим (родителям), использовать всплывающую подсказку событий и указывать только один обработчик событий для управления всеми событиями определенного типа.

Давайте взглянем.

ul.onclick = function (e){
	console.log(e.target);
}

Это делегирование событий.

  • преимущество:
    • Это может сэкономить много памяти и уменьшить количество регистраций событий.
    • Можно реализовать, что при добавлении дочернего объекта для него не требуется привязка событий, что особенно подходит для динамического контента.
  • недостаток:
    • Обычное применение прокси событий должно быть ограничено вышеуказанными требованиями.Если все события используются в качестве прокси событий, может произойти ошибочная оценка событий. То есть события, которые не должны запускаться, привязываются к событиям.

Исправление:

существуетChrome 89.0.4363.0и в более поздних версияхПорядок срабатывания событий для целевых элементов больше не срабатывает в том порядке, в котором они были зарегистрированы! Вместо этого он выполняется последовательно в виде сначала захвата, а затем всплытия!