JavaScript事件模型
在JavaScript中,有两种事件的传递方向,一种是由内层元素向外层元素传递,也叫自底向上的方式,称作事件冒泡,好比水中的气泡由水底向水面上升的过程。另一种叫做事件捕获,方向刚好相反,从外层元素向内层元素传递,也叫自顶向下。
目前主流的浏览器都支持这两种事件传递方式,但是在IE8及以下版本的浏览器中,只支持事件冒泡,不支持事件捕获。
所以DOM中的事件处理分为以下三个阶段
- capture(捕获阶段),事件由外层向内层传递
- target(命中阶段),事件到达目标元素
- bubbling(冒泡阶段),事件由内层向外层传递
那么如何指定事件的传递方式呢?我们可以通过addEventListener的第三个参数来指定,比如下面的代码:
当useCapture为true时,事件传递方式为事件捕获,当useCapture为false时,事件传递方式为事件冒泡。默认值为false,使用事件冒泡模式。
1 | addEventListener(type, listener, useCapture) |
Event.stopPropagation
- 当事件传递方式为捕获模式时,
event.stopPropagation()
会阻止事件继续向下(内层元素)传递。 - 当事件传递方式为冒泡模式时,
event.stopPropagation()
会阻止事件继续向上(外层元素)传递。
代码示例:
1 | <div id="div1"> |
1 | const div1 = document.querySelector("#div1"); |
点击div3
,输出如下,因为采用的是冒泡模式,所以事件会从内层元素向外层元素传递。即div3
最先捕获事件,然后是冒泡给div2
,最后是div1
.
1 | div3 clicked |
如果我们在clickHandler3中加入event.stopPropagation()
,再次点击div3,输出如下:
1 | div3 clicked |
可见,event.stopPropagation()
阻止了事件继续向上(外层元素)传递。
将事件处理函数改为捕获模式
1 | bindEventListener(div1, "click", clickHandler1, true); |
再次点击div3,输出如下,因为采用的是捕获模式,所以事件会从外层元素向内层元素传递。即div1
最先捕获事件,然后是div2
,最后是div3
.
1 | div1 clicked |
如果我们在clickHandler1中加入event.stopPropagation()
,再次点击div3,输出如下:
1 | div1 clicked |
可见,event.stopPropagation()
阻止了事件继续向下(内层元素)传递。
Event.stopImmediatePropagation
如果将上述代码中的event.stopPropagation()
改为event.stopImmediatePropagation()
,你会发现,输出的结果是一样的,这说明event.stopImmediatePropagation()
与event.stopPropagation()
的作用是一样的,都是阻止事件继续传递。既然作用是一样的,那么为什么还要有event.stopImmediatePropagation()
呢?这是因为event.stopImmediatePropagation()
还有一个额外的功能,就是阻止事件处理函数队列中的其他函数执行,比如下面的代码:
1 | bindEventListener(div1, "click", clickHandler1, false); |
当我们点击div1时,输出如下:
1 | div1 clicked |
当多个事件处理函数绑定到同一个元素的同一个事件时,事件处理函数的执行顺序是按照绑定的顺序执行的,比如上面的代码,clickHandler1会先于clickHandler2执行,clickHandler2会先于clickHandler3执行。如果我们在clickHandler1中加入event.stopImmediatePropagation()
,再次点击div1,输出如下:
1 | div1 clicked |
可见,event.stopImmediatePropagation()
阻止了事件处理函数队列中的其他函数执行。clickHandler2和clickHandler3都被阻止了执行。
阻止默认行为
event.stopPropagation()
虽然能阻止事件传播,却不能阻止事件的默认行为,比如将上例中的button换成<a>
的话,即使阻止了事件传播,点击链接后a标签依然会跳转。这时,我们可以使用 event.preventDefault()
来实现这个功能。
1 | <html lang="zh-Hans-CN"> |
1 | document.getElementById('my-div').addEventListener('click', divClickHandler, true); |
有以下几点需要注意:
event.preventDefault()
只会阻止事件默认行为,并不会阻止事件继续传播event.preventDefault()
只对cancelable=true的事件起作用。
event.preventDefault()
的应用场景有:
阻止
<a>
标签点击后跳转阻止
<checkbox>
被选中验证用户输入,比如只允许输入小写字母,当输入非小写字母时,不显示输入的字符。
1
2
3
4
5
6
7
8
9
10
11
12function checkName(evt) {
var charCode = evt.charCode;
if (charCode != 0) {
if (charCode < 97 || charCode > 122) {
evt.preventDefault();
displayWarning(
"Please use lowercase letters only."
+ "\n" + "charCode: " + charCode + "\n"
);
}
}
}