文章目录
- 前言
- 一、以前两种事件流分析
-
- 1.冒泡事件流
- 2.捕获事件流
- 结合:
- 二、JS事件流
-
- 1.原理
-
- 1.on属性问题:
- 2.addEventLIstener()问题
- 三、阻止事件传递
-
- 1.Event.stopPropagation() 方法
- 2.Event.stopImmediatePropagation()方法
- 3.cancelBubble
前言
JS事件流问题也可以理解为事件触发顺序问题,而JS事件流最早要从IE和网景公司的浏览器大战说起,当浏览器发展到第四代时,IE提出的是冒泡流,而网景提出的是捕获流,两种事件流形式完全相反。
一、以前两种事件流分析
1.冒泡事件流
个人理解是和冒泡排序原理相似,即事件有最具体的接收,之后逐渐向上冒泡,传到页面。(即由子元素逐渐向父元素传)
以button按钮为例:
2.捕获事件流
与冒泡事件流原理完全相反,事件从页面元素开始接收,逐级向下到最具体的元素。
同样以button按钮为例:
结合:
后来在W3C组织的统一之下,JS支持了冒泡流和捕获流,两种事件流在DOM2级事件中得到了统一,相对于DOM0事件类型,DOM2级有以下特点:
- 允许某个元素绑定多个同类型事件(可以拿addEventLIstener事件与on事件为例)。
- 规定了事件流的三个阶段:捕获阶段,目标阶段,冒泡阶段。
注意用Event.eventPhase可以返回当前所在的阶段:
var phase = event.eventPhase;
0,事件目前没有发生。
1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。
2,事件到达目标节点,即Event.target属性指向的那个节点。
3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。
二、JS事件流
1.原理
JS事件的原理图如下:
由图可知:
- 现在的JS事件流是由捕获事件流和冒泡事件流组合形成的,即由window开始最后回到window的过程。
- 事件流被分为三个阶段(1-5)捕获过程、(5-6)目标过程、(6~10)冒泡过程。
- 这个过程决定了事件点击时的执行顺序。
1.on属性问题:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div style="width:200px;height:200px;background:lightblue" id="content">
<button style="width:100px;height:50px;" id="btn1">
</button>
</div>
</body>
<script type="text/javascript">
var content = document.getElementById("content");
var btn1 = document.getElementById('btn1');
btn1.onclick = function(){
alert("button");
};
content.onclick = function(){
alert("content");
}
//点击button的结果是先打印button之后再打印content
</script>
</html>
由执行结果可知,on-属性的执行是在冒泡阶段发生的,对于EventTarget.addEventListener()的原理也是一样的。
2.addEventLIstener()问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset = "UTF-8">
<title>Document</title>
</head>
<body>
<div style = "width:200px;height:200px;background:lightblue" id="content">
<button style = "width:100px;height:50px;" id="btn1">
</button>
</div>
</body>
<script type = "text/javascript">
var content = document.getElementById("content");
var btn1 = document.getElementById('btn1');
btn1.addEventListener('click', function () { alert("button的冒泡阶段"); }, false);
btn1.addEventListener('click', function () { alert("button的捕获阶段"); }, true);
content.addEventListener('click', function () { alert('content的冒泡阶段'); }, false);
content.addEventListener('click', function () { alert('content的捕获阶段'); }, true);
//结果为:content的捕获阶段
// button的冒泡阶段
// button的捕获阶段
// content的冒泡阶段
</script>
</html>
注意EventTarget.addEventListener()监听函数useCapture值为false时表示只在冒泡阶段被触发
三、阻止事件传递
通过之前的描述可以轻易看出,事件流中某元素被触发之后会造成连锁反应:会导致其容器元素同类型的事件被触发,有时候,这并不是我们所需要的所以,我们需要进行阻止。
1.Event.stopPropagation() 方法
stopPropagation()方法可以阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上其他的事件监听函数。
上代码:
function stopEvent(e) {
e.stopPropagation();
}
el.addEventListener('click', stopEvent, false);
此时,click事件将不会进一步冒泡到el节点的父节点。
2.Event.stopImmediatePropagation()方法
此方法与之前的方法最大的区别在于其阻止的更加彻底,可以阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。
如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了Event.stopImmediatePropagation()方法,其他的监听函数就不会再执行了。
上代码:
function l1(e){
e.stopImmediatePropagation();
}
function l2(e){
console.log('hello world');
}
el.addEventListener('click', l1, false);
el.addEventListener('click', l2, false);
上面代码在el节点上,为click事件添加了两个监听函数l1和l2。由于l1调用了event.stopImmediatePropagation方法,所以l2不会被调用。
3.cancelBubble
cancelBubble是IE标准下阻止事件传递的属性,设置cancelBubble=true表示阻止冒泡。
它与stopPropagation()兼容性对比如下:
所以,注意:
- 若需要在捕获阶段阻止事件传递,使用addEventListener()进行事件监听,同时使用e.stopPropagation()阻止冒泡。
- 若需要在冒泡阶段阻止事件传递,在兼容addEventListener()和attachEvent()的同时,为保险起见,需要对阻止事件传递方式进行兼容。以下仅表示在DOM2及事件模型下兼容方式:
if (e.stopPropagation){
e.stopPropagation();
}else{
e.cancelBubble = true;
}