DOM事件详解

DOM 事件级别

DOM 级别分为四个级别:DOM0 级、DOM1 级、DOM2 级、DOM3 级。

image
DOM 事件级别分为三个级别:

  1. DOM0 级事件
    1
    2
    3
    4
    5
    6
    7
    8
    <button id="btn" type="button"></button>
    <script>
    var btn = document.getElementById('btn')
    btn.onclick = function() {
    console.log('Hello World')
    }
    // btn.onclick = null // 解绑事件
    </script>

缺点:无法设置多个事件处理函数

  1. DOM2 级事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <button id="btn" type="button"></button>
    <script>
    var btn = document.getElementById('btn');
    btn.addEventListener('click', showFn, false)
    btn.addEventListener('click', showFn2, false)
    // btn.removeEventListener('click', showFn, false) // 解绑事件
    function showFn() {
    alert('Hello World');
    }
    function showFn2() {
    alert('Hello World2');
    }
    </script>

可以为事件设置多个事件处理函数,可以通过第三个参数 ( useCapture ) 设置在什么阶段执行事件处理函数,默认是 false, 即在事件冒泡阶段执行事件处理函数。

需要注意的是在 IE8 及以下版本需要用 attachEvent 和 detachEvent 实现,只有两个参数,事件名需要以 on 开头,只支持在事件冒泡阶段执行事件处理函数。

  1. DOM3 级事件

DOM3 级事件是在 DOM2 级事件的基础上添加了更多的事件类型,允许自定义事件。

  • UI事件,当用户与页面上的元素交互时触发,如:load、scroll
  • 焦点事件,当元素获得或失去焦点时触发,如:blur、focus
  • 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dbclick、mouseup
  • 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
  • 文本事件,当在文档中输入文本时触发,如:textInput
  • 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
  • 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
  • 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified
1
2
3
4
5
6
7
8
9
10
11
// 自定义事件
var event = new Event('test')
// 给元素绑定事件
domElement.addEventListener('test', function() {
console.log('event test')
},)

// 触发事件
setTimeout(function() {
domElement.dispatchEvent(event)
}, 1000)

DOM 事件流

想象画在一张纸上的一组同心圆。如果把手指放在圆心上,那么手指指向的不仅仅是一个圆,而是纸上的所有圆。所以如果点击了某个按钮,点击事件不仅仅发生在这个按钮上,整个页面也被点击了。

事件流又称为事件传播,描述的是从页面中接收事件的顺序。DOM2 级事件规定事件流包括三个阶段: 事件捕获(capturing phase)目标事件(target phase)事件冒泡(bubbling phase)

因为有两种观点,所以事件流也有两种,分别是事件冒泡和事件捕获。现行的主流是事件冒泡。

事件冒泡

事件冒泡即事件开始时,由最具体的元素接收(也就是事件发生所在的节点),然后逐级传播到较为不具体的节点。
举个栗子,就很容易明白了。

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Bubbling</title>
</head>
<body>
<button id="clickMe">Click Me</button>
</body>
</html>

然后,我们给button和它的父元素,加入点击事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var button = document.getElementById('clickMe');

button.onclick = function() {
console.log('1. You click Button');
};
document.body.onclick = function() {
console.log('2. You click body');
};
document.onclick = function() {
console.log('3. You click document');
};
window.onclick = function() {
console.log('4. You click window');
};

结果如下图所示:
image
也就是说,click事件首先在button元素上发生,然后逐级向上传播。这就是事件冒泡。

事件捕获

事件捕获的概念,与事件冒泡正好相反。它认为当某个事件发生时,父元素应该更早接收到事件,具体元素则最后接收到事件。比如说刚才的demo,如果是事件捕获的话,事件发生顺序会和上面相反。

当然,由于时代更迭,事件冒泡方式更胜一筹。所以放心的使用事件冒泡,有特殊需要再使用事件捕获即可。

addEventlistener在事件流中的运用

addEventlistener的定义以及参数:
image

改用事件捕获模式

例如,将上面的例子修改一下:

1
2
3
window.addEventListener('click', function() {
console.log('4. You click window');
}, true);//useCapture位设置为true,使用事件捕获模式

结果:
image

阻止事件冒泡

再比如,事件冒泡过程,是可以被阻止的。防止事件冒泡而带来不必要的错误和困扰。
这个方法就是:stopPropagation()

我们对button的click事件做一些改造:

1
2
3
4
5
6
button.addEventListener('click', function(event) {
// event为事件对象
console.log('1. You click Button');
event.stopPropagation();
console.log('Stop Propagation!');
}, false);

结果如下:
image
不难看出,事件在到达具体元素后,停止了冒泡。但不影响父元素的事件捕获。

参考

https://juejin.im/post/5c4bd01fe51d45522b4f6e4e#heading-5
https://segmentfault.com/a/1190000004463384