基本概念
事件代理(Event Delegation),又称事件委托。
事件委托是JavaScript中常用绑定事件的常用技巧,是把原本需要绑定在子元素的响应事件(click、keydown……)委托给父元素,让父元素担当事件监听的职务。
事件代理的原理是DOM元素的事件冒泡。
![[js基础]冒泡原理:事件委托机制 [js基础]冒泡原理:事件委托机制](https://www.bmabk.com/wp-content/uploads/2022/05/post-loading.gif)
如上图所示,事件传播分成三个阶段:
-
捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件; -
目标阶段:在目标节点上触发,称为“目标阶段” -
冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;
事件冒泡的优点和缺点
优点:
-
那些需要创建的以及驻留在内存中的事件处理器少了,并降低了崩溃的风险。
-
在DOM更新后无须重新绑定事件处理器了。如果页面是动态生成的,比如说通过Ajax,你不再需要在元素被载入或者卸载的时候来添加或者删除事件处理器了。
-
可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件就非常棒
-
可以实现当新增子对象时无需再次对其绑定(动态绑定事件)
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li
如上面代码所示,如果给每个li列表项都绑定一个函数,那对内存的消耗是非常大的,因此较好的解决办法就是将li元素的点击事件绑定到它的父元素ul身上,执行事件的时候再去匹配判断目标元素。
缺点:
-
捕获比冒泡先执行。
-
鼠标的拖放事件,一般用捕获传递。因为这样不会使内部的元素触发处理事件,而且当鼠标down的时候且鼠标移动到document画布上时候,移动目标元素的移动事件依然可以有效。IE9之前IE只支持冒泡。
-
事件管理代码有成为性能瓶颈的风险,所以尽量使它能够短小精悍。
能冒泡的事件
每个 event 都有一个event.bubbles
属性,可以知道它可否冒泡。
(ref:W3定义的Event Interface)
Event Type | Bubbling phase |
---|---|
abort | ✗ |
beforeinput | ✔ |
blur | ✗ |
click | ✔ |
compositionstart | ✔ |
compositionupdate | ✔ |
compositionend | ✔ |
dblclick | ✔ |
error | ✗ |
focus | ✗ |
focusin | ✔ |
focusout | ✔ |
input | ✔ |
keydown | ✔ |
keyup | ✔ |
load | ✗ |
mousedown | ✔ |
mouseenter | ✗ |
mouseleave | ✗ |
mousemove | ✔ |
mouseout | ✔ |
mouseover | ✔ |
mouseup | ✔ |
resize | ✗ |
scroll | ✔ |
select | ✔ |
unload | ✗ |
wheel | ✔ |
还附上了 Legacy Events(旧浏览器支持的非标准遗留事件)的 bubble 属性。
Event Type | Bubbling phase |
---|---|
DOMActivate | ✔ |
DOMAttrModified | ✔ |
DOMCharacterDataModified | ✔ |
DOMFocusIn | ✔ |
DOMFocusOut | ✔ |
DOMNodeInserted | ✔ |
DOMNodeInsertedIntoDocument | ✗ |
DOMNodeRemoved | ✔ |
DOMNodeRemovedFromDocument | ✗ |
DOMSubtreeModified | ✔ |
keypress | ✔ |
H5 还定义了一些新事件:
-
media相关事件,都不冒泡 -
drag相关事件 dragstart
、drag
、dragenter
、dragexit
、dragleave
、dragover
、drop
、dragend
均冒泡 -
History相关事件: popstate
,hashchange
冒泡(从window开始……所以意义在哪里),pagetransition
不冒泡
还有很多H5新事件,大多在草案阶段,就不一一翻开了。
此外,这里还有一个关于IE的事件列表,http://www.feiesoft.com/html/events.html
事件冒泡是我们实现事件代理(委托)的关键,在avalon1.6中,默认让能冒泡的事件都使用事件代理实现了!
let canBubbleUp = {
click: true,
dblclick: true,
keydown: true,
keypress: true,
keyup: true,
mousedown: true,
mousemove: true,
mouseup: true,
mouseover: true,
mouseout: true,
wheel: true,
mousewheel: true,
input: true,
change: true,
beforeinput: true,
compositionstart: true,
compositionupdate: true,
compositionend: true,
select: true,
cut: true,
paste:true,
focusin: true,
focusout: true,
DOMFocusIn: true,
DOMFocusOut: true,
DOMActivate: true,
dragend:true,
datasetchanged:true
}
if (!W3C) {
delete canBubbleUp.change
delete canBubbleUp.select
}
//....
基本实现
原生
![[js基础]冒泡原理:事件委托机制 [js基础]冒泡原理:事件委托机制](https://www.bmabk.com/wp-content/uploads/2022/05/post-loading.gif)
可以在捕捉到的event中找到path这一条属性,这里记录了捕获阶段的路径。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="id-my-links">
<li id="id_1">这是1</li>
<li id="id_2">这是2</li>
<li id="id_3">这是3</li>
</ul>
</body>
<script>
let item1 = document.getElementById("id_1");
let item2 = document.getElementById("id_2");
let item3 = document.getElementById("id_3");
// item1.onclick = function () {
// console.log(this)
// };
// item2.onclick = function () {
// console.log(this)
// };
// item3.onclick = function () {
// console.log(this)
// };
let eMyLinks = document.getElementById("id-my-links")
eMyLinks.addEventListener("click", function (event) {
let target = event.target;
// console.log(event);
console.log(window.event.srcElement);
console.log(target);
switch (target.id) {
case "id_1":
console.log(this)
break;
case "id_2":
console.log(this)
break;
case "id_3":
console.log(this)
break;
}
})
</script>
</html>
再来看另一个例子,实现鼠标移入item的时候加样式。
![[js基础]冒泡原理:事件委托机制 [js基础]冒泡原理:事件委托机制](https://www.bmabk.com/wp-content/uploads/2022/05/post-loading.gif)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
<button id="id_btn_01">添加新结点</button>
<script type="text/javascript">
window.onload = function () {
let oUl = document.getElementById('ul1');
let oBtn = document.getElementById('id_btn_01')
let numLi = 0;
oUl.onmouseover = function (ev) {
console.log("当时移入:")
console.log(event.srcElement)
ev = ev || window.event;
let oLi = ev.srcElement || ev.target;
if (oLi.nodeName.toLowerCase() == 'li') {
oLi.style.background = 'red';
}
}
oUl.onmouseout = function (ev) {
console.log("当时移出:")
console.log(event.srcElement)
ev = ev || window.event;
let oLi = ev.srcElement || ev.target;
if (oLi.nodeName.toLowerCase() == 'li') {
oLi.style.background = '';
}
}
oBtn.onclick = function(){
numLi++;
let oLi = document.createElement('li')
oLi.innerHTML = 111*numLi;
oUl.appendChild(oLi);
}
}
</script>
</body>
</html>
小结: 精髓是下面两句。
// 正确捕捉到event
let ev = ev || window.event;
// 正确捕捉到发生event的元素
let oLi = ev.srcElement || ev.target;
jQuery事件delegate()实现事件委托
delegate() 方法为指定的元素(属于被选元素的子元素)添加一个或多个事件处理程序,并规定当这些事件发生时运行的函数。
格式:$(selector).delegate(childSelector, event, data, function)
参数 | 描述 |
---|---|
childSelector | 必需,规定要附加事件处理程序的一个或多个子元素。 |
event | 必需,规定附加到元素的一个或多个事件。由空格分隔多个事件值。必须是有效的事件。 |
data | 可选,规定传递到函数的额外数据。 |
function | 必需,规定当事件发生时运行的函数。 |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script>
</head>
<body>
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
<script>
$(document).ready(function () {
$("#myLinks").delegate("#goSomewhere", "click", function () {
location.href = "http://www.baidu.com";
});
});
</script>
</body>
</html>
取消冒泡
冒泡机制有好有坏,有时候只想触发子元素的点击事件而不触发父类的点击,那么这时候就需要阻断冒泡了。
阅读如下代码。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script type="text/javascript">
window.onload = function () {
document.getElementById('dv').onclick = function () {
// console.log(window.event.srcElement);
console.log(this)
};
document.getElementById('p1').onclick = function () {
// console.log(window.event.srcElement);
console.log(this)
}
document.getElementById('sp').onclick = function () {
// console.log(window.event.srcElement);
console.log(this)
}
}
</script>
</head>
<body>
<div id="dv" style="width: 300px;height: 300px;background: yellow;">
<p id="p1" style="width: 150px;height: 150px;background: green;">
<span id="sp" style="background: red;">Hello!</span>
</p>
</div>
</body>
</html>
上面的代码中,看看event的指向在冒泡过程中会不会改变。
![[js基础]冒泡原理:事件委托机制 [js基础]冒泡原理:事件委托机制](https://www.bmabk.com/wp-content/uploads/2022/05/post-loading.gif)
![[js基础]冒泡原理:事件委托机制 [js基础]冒泡原理:事件委托机制](https://www.bmabk.com/wp-content/uploads/2022/05/post-loading.gif)
下面再来看看this的指向在冒泡过程中如何改变。
![[js基础]冒泡原理:事件委托机制 [js基础]冒泡原理:事件委托机制](https://www.bmabk.com/wp-content/uploads/2022/05/post-loading.gif)
![[js基础]冒泡原理:事件委托机制 [js基础]冒泡原理:事件委托机制](https://www.bmabk.com/wp-content/uploads/2022/05/post-loading.gif)
可以发现,event的指向在冒泡过程中,不会改变,而this指针则是冒泡到哪就指向哪个元素。
一般而言,阻止冒泡用
window.event.cancelBubble = true;
改进后的代码如下所示。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script type="text/javascript">
window.onload = function() {
document.getElementById('dv').onclick = function() {
console.log(window.event.srcElement);
console.log(this.id);
window.event.cancelBubble = true;
};
document.getElementById('p1').onclick = function() {
console.log(window.event.srcElement);
console.log(this.id);
window.event.cancelBubble = true;
}
document.getElementById('sp').onclick = function() {
console.log(window.event.srcElement);
console.log(this.id);
window.event.cancelBubble = true;
}
}
</script>
</head>
<body>
<div id="dv" style="width: 300px;height: 300px;background: yellow;">
<p id="p1" style="width: 150px;height: 150px;background: green;">
<span id="sp" style="background: red;">Hello!</span>
</p>
</div>
</body>
</html>
《参考资料》
通过事件冒泡来认识 this 和 event 的区别
JavaScript事件代理(事件委托)
原文始发于微信公众号(豆子前端):[js基础]冒泡原理:事件委托机制
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/56761.html