深入理解 DOM 事件机制
一、DOM 事件模型
DOM 事件模型包括捕获和冒泡,捕获是从上往下到达目标元素,冒泡是从当前元素,也就是目标元素往上到 window。
1、事件冒泡
事件开始时由具体元素接收,然后逐级向上传播到父元素。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Bubbling</title>
</head>
<body>
<div id="clickMe">Click Me</div>
</body>
</html>
我们给 div 和它的父元素,加入点击事件:
var btn = document.getElementById('clickMe');
btn.onclick = function () {
console.log('1. click Button');
};
document.body.onclick = function () {
console.log('2. click body');
};
document.onclick = function () {
console.log('3. click document');
};
window.onclick = function () {
console.log('4. click window');
};
// 输出:
// 1. click Button
// 2. click body
// 3. click document
// 4. click window
从输出结果可看出,click 事件首先在 <div>
元素上发生,然后逐级向上传播,这就是事件冒泡。
2、事件捕获
事件开始时由父元素接收,然后逐级向下传播到子元素,与事件冒泡相反。
二、事件流
1、什么是流
流的概念,在 JS 中随处可见,比如说 React 的单向数据流,Node 中的流,还有 DOM 事件流,都是流的一种生动体现。
至于流的具体概念,用术语说流是对输入输出设备的抽象。以程序的角度说,流是具有方向的数据。
事件流所描述的就是从页面中接受事件的顺序,事件流分为两种:事件冒泡(主流)和事件捕获。
2、DOM 事件流
DOM事件流包括三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
2-1、事件捕获阶段
当事件发生时,首先发生的是事件捕获,为父元素截获事件提供了机会。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Bubbling</title>
</head>
<body>
<div id="clickMe">Click Me</div>
</body>
</html>
上面事件冒泡的 Demo 中,window 点击事件更改为使用事件捕获模式:
var btn = document.getElementById('clickMe');
btn.onclick = function () {
console.log('1. click Button');
};
document.body.onclick = function () {
console.log('2. click body');
};
document.onclick = function () {
console.log('3. click document');
};
// window.onclick = function() {
// console.log('4. click window');
// };
window.addEventListener('click', function () {
console.log('4. click window');
}, true);
// 输出:
// 4. click window
// 1. click Button
// 2. click body
// 3. click document
从输出结果可看出,点击事件先被父元素截获了,且该函数只在事件捕获阶段起作用。
2-2、处于目标阶段
事件到了具体元素时,在具体元素上发生,并且被看成冒泡阶段的一部分。
2-3、事件冒泡阶段
最后,冒泡阶段发生,事件开始冒泡。
三、如何阻止事件冒泡
事件冒泡过程,是可以被阻止的,防止事件冒泡而带来不必要的错误和困扰。
阻止方法是使用 stopPropagation()
,示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Bubbling</title>
</head>
<body>
<div id="clickMe">Click Me</div>
</body>
</html>
还是上面的 demo,这里对 div 的 click 事件做了一些改造:
var btn = document.getElementById('clickMe');
// btn.onclick = function() {
// console.log('1. click Button');
// };
btn.addEventListener('click', function (event) {
// 这里 event 为事件对象
console.log('1. click Button');
event.stopPropagation();
console.log('Stop Propagation!');
}, false);
document.body.onclick = function () {
console.log('2. click body');
};
document.onclick = function () {
console.log('3. click document');
};
window.addEventListener('click', function () {
console.log('4. click window');
}, true);
// 输出:
// 4. click window
// 1. click Button
// Stop Propagation!
从输出结果可看出,事件在到达具体元素后,停止了冒泡,但不影响父元素的事件捕获。
四、DOM 事件级别
DOM 级别分为 4 个级别:DOM 0级、DOM 1级、DOM 2级、DOM 3级。
DOM 事件分为 3 个级别:DOM 0级事件处理、DOM 2级事件处理、DOM 3级事件处理。
由于 DOM 1级中没有事件的相关内容,所以没有 DOM 1级事件。
1、DOM 0级事件
el.onclick = function () { }
DOM 0级事件,就是直接通过 onclick 等方式实现相应的事件。
btn.onclick = function () {
alert('Hello1');
}
btn.onclick = function () {
alert('Hello2');
}
DOM 0级事件无法为同一个元素绑定多个同类型事件,例如:
上面的代码点击按钮只弹出:
可以看出 DOM 0级添加事件时,后面的事件会覆盖前面的事件。
注意: DOM 0级事件只存在这两个阶段:
- 处于目标阶段
- 事件冒泡阶段
另外,DOM 0级事件具有很好的跨浏览器优势,会以最快的速度绑定,但由于绑定速度太快,可能页面还未完全加载出来,以至于事件可能无法正常运行。
2、DOM 2级事件
添加事件时,DOM 2级事件不会覆盖前面的事件,多个事件都会执行。
方法:
- target.addEventListener(type, callback[, useCapture]);
- target.removeEventListener(type, callback[, useCapture]);
参数:
- type(必须):事件类型,如 'click'、'mouseover'、'mouseout' 等。
- callback(必须):事件触发的回调函数,会注入当前的事件对象 event 作为参数。
- useCapture(可选):默认为
false
,如果为true
,则表示在捕获阶段调用事件处理程序,反之在事件冒泡阶段处理。
示例:
<div id="clickMe" onclick="alert('Hello1');" >clickMe</div>
btn.onclick = function () {
alert('Hello2');
}
btn.addEventListener('click', function () {
alert('Hello3');
}, true)
btn.addEventListener('click', function () {
alert('Hello4');
}, true)
btn.addEventListener('click', function () {
alert('Hello5');
}, false)
运行结果:
注意: DOM 2级事件包含这三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
3、DOM 3级事件
DOM 3级事件在 DOM 2级事件的基础上添加了更多的事件类型:
- UI事件:当用户与页面上的元素交互时触发,如:load、scroll。
- 焦点事件:当元素获得或失去焦点时触发,如:blur、focus。
- 鼠标事件:当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup。
- 滚轮事件:当使用鼠标滚轮或类似设备时触发,如:mousewheel。
- 文本事件:当在文档中输入文本时触发,如:textInput。
- 键盘事件:当用户通过键盘在页面上执行操作时触发,如:keydown、keypress。
- 合成事件:当为 IME(输入法编辑器)输入字符时触发,如:compositionstart。
- 变动事件:当底层 DOM 结构发生变化时触发,如:DOMsubtreeModified。
- 同时DOM3级事件也允许使用者自定义一些事件。