事件流、事件委托/事件代理
事件流
JS 与 html 页面的交互是通过 DOM 事件实现的,那么什么是事件流?其实就是指页面接收事件的顺序;、
DOM 事件流包括三个阶段:事件捕获阶段,处于目标阶段,事件冒泡阶段;
用意下代码为简单示例说明:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event</title>
</head>
<body>
<button>我是一个按钮</button>
</body>
</html>
事件捕获
事件触发后,不具体的节点应该更早接收到事件,最具体的节点应该最后接收到事件,其用意在于在事件到达预定目标之前捕获它;
在示例页面中,当点击 <button>
元素,click 事件传播顺序:
Document -> html
-> body
-> button
由外到内
事件冒泡
与事件捕获正好相反,事件开始时,由最具体的元素(事件发生所在的节点)接收,然后逐级向上传播到较为不具体的节点(文档);
在示例页面中,当点击 <button>
元素,click 事件传播顺序:
<button>
-> body
-> html
-> Document 由内到外
结论:触发一个事件,首先事件捕获,为截获事件提供机会,然后实际的目标接收到事件,最后事件冒泡阶段,可在这个阶段对事件做出响应;
阻止事件触发
默认情况下,同时存在多个事件处理函数时,会按照 DOM 事件流模型中的顺序执行;当子元素上发生某个事件,不需要执行父元素上注册的事件处理函数,可以停止事件捕获和冒泡,避免过多没有意义的函数调用;
常见的3中阻止事件触发的方式:
- event.preventDefault()
用于阻止特定事件的默认动作;
只有 cancelable 为 true 的事件才可以用,若对象的 cancelable 为 false, 那就是没有默认动作,或不能阻止默认动作;
cancelable:是事件对象(event)上的属性,event.cancelable
返回一个布尔值, 如用preventDefault()
方法可以取消与事件关联的默认动作,则为 true,否则为 fasle;
示例:点击type="submit"
的 input 标签提交表单,在 onclick 事件函数中用event.preventDefault()
阻止默认动作, 点击 submit 后就不会自动提交表单,但是不能阻止事件冒泡; - event.stopPropagation()
立即停止事件在 DOM 中的传播,不阻止默认动作;
示例:点击一个按钮,只想触发绑定在按钮上的 click 事件函数,不想触发传播链上的其他函数,可以使用这种方式event.stopPropagation()
;
注意:若是在捕获流就执行到的事件,比如在 document 上绑定一个点击事件,addEventListen 的第三个参数设置为 true,那么event.stopPropagation
无法阻止触发; - return false
后续所有相关的触发事件和动作都不会被执行,阻止事件继续传播,事件冒泡和默认行为都被阻止;
jQuery 提供,实际做了三件事:- event.preventDefault()
- event.stopPropagation()
- 停止回调函数执行并立即返回
事件委托/事件代理
事件委托:又名事件代理,利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件;
优点:
1. 事件管理函数减少,不需要为每个元素都添加监听函数,对于同意父节点下相似的子元素,可以通过委托给父元素的监听函数来处理事件;
2. 可以更方便的动态添加和修改元素,不需要因元素的改动而修改事件绑定;
3. 极大程度上减少了与 DOM 的交互次数,从而提高整体运行性能,事件委托是 "事件处理程序过多" 问题的直接解决方案;
一个简单的事件委托示例:
点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件委托</title>
</head>
<body>
<div id="root">
<button id="add">添加</button>
<button id="modify">修改</button>
<button id="move">移动</button>
<button id="delete">删除</button>
</div>
<script>
window.onload = function() {
// 普通方式
const addDom = document.getElementById("add");
const modifyDom = document.getElementById("modify");
const moveDom = document.getElementById("move");
const deleteDom = document.getElementById("delete");
addDom.onclick = function(e) {
console.log("click add");
};
modifyDom.onclick = function(e) {
console.log("click modify");
};
moveDom.onclick = function(e) {
console.log("click move");
};
deleteDom.onclick = function(e) {
console.log("click delete");
};
// 事件委托方式
const root = document.getElementById("root");
root.onclick = function(e) {
const event = e || window.event;
const target = event.target || window.srcElement;
if (target.nodeName.toLowerCase() === "button") {
switch (target.id) {
case "add":
console.log("add");
break;
case "modify":
console.log("modify");
break;
case "move":
console.log("move");
break;
case "delete":
console.log("delete");
break;
default:
console.log("无效的点击类型");
break;
}
}
}
};
</script>
</body>
</html>
使用普通方式点击每一个做不同的操作,至少需要4次DOM操作,用事件委托就可以只用一次DOM操作就能实现所有效果;
适合使用事件委托的事件: click、mousedown、mouseup、keydown、keyup、keypress
注意: mouseover 和 mouseout 事件虽然也冒泡,但是处理它们的时候需要计算元素位置,非常不好把控,focus,blur 之类本身就没用冒泡的特性,所以不能使用事件委托;
总结
事件流是页面接收事件的顺序:捕获阶段 -> 目标阶段 -> 冒泡阶段;
事件委托又名事件代理,利用事件冒泡,只指定一个事件处理程序,就可以管理某种类型的所有事件;极大程度上减少与 DOM 的交互次数,提高整体运行性能;