JavaScript addEventListener 事件监听与冒泡捕获详解
eventTarget
EventTarget是一个DOM接口,由可以接收事件、并且可以创建侦听器的对象实现。
addEventListener
- 将指定的监听器注册到
EventTarget上,当该对象触发指定的事件时,指定的回调函数就会被执行。 - 事件目标可以是一个文档上的元素
Element,Document和Window或者任何其他支持事件的对象 (比如XMLHttpRequest)。 addEventListener()的工作原理是将实现EventListener的函数或对象添加到调用它的EventTarget上的指定事件类型的事件侦听器列表中。- 语法
target.addEventListener(type, listener, options);target.addEventListener(type, listener, useCapture);type:表示监听事件类型的字符串。listener- 当所监听的事件类型触发时,会接收到一个事件通知(实现了
Event接口的对象)对象。listener必须是一个实现了EventListener接口的对象,或者是一个函数。
- 当所监听的事件类型触发时,会接收到一个事件通知(实现了
options可选。一个指定有关listener属性的可选参数对象。可用的选项如下:capture:Boolean,表示listener是否使用捕获事件,意味着事件触发将从根元素到目标节点。once:Boolean,表示listener在添加之后最多只调用一次。如果是true,listener会在其被调用之后自动移除。passive:Boolean,设置为true时,表示listener永远不会调用preventDefault()。如果listener仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
useCapture: 可选Boolean,在DOM树中,注册了listener的元素,是否要先于它下面的EventTarget,调用该listener。 当useCapture(设为true) 时,沿着DOM树向上冒泡的事件。如果没有指定,useCapture默认为false。
- 返回值
undefined.
事件冒泡捕获
DOM 事件流
- 事件流所描述的就是从页面中接受事件的顺序。
DOM事件流存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。- 浏览器在为这个当前页面与用户做交互的过程中,比如我点击了这个鼠标左键,这个左键是怎么传到页面上。 还有怎么响应的。事件流分三个阶段,第一阶段是捕获,第二阶段是目标阶段, 比如点击的这个按钮,这个按钮就是目标阶段,事件通过捕获到达目标元素,就到达了目标阶段, 第三个阶段是冒泡阶段,从目标元素再上传到
window对象,就是冒泡的过程。
事件传播——冒泡与捕获
默认情况下,事件使用冒泡事件流,不使用捕获事件流。然而,在 Firefox 和 Safari 里, 你可以显式的指定使用捕获事件流,方法是在注册事件时传入 useCapture 参数,将这个参数设为 true。
冒泡事件流
- 事件冒泡即事件开始时,由最具体的元素接收(也就是事件发生所在的节点),然后逐级传播到较为不具体的节点。
- 阻止事件冒泡可以通过调用事件对象上的
stopPropagation()方法, 在Internet Explorer里可以通过设置事件对象的cancelBubble属性为true。 - 如果不停止事件的传播,事件将一直通过 DOM 冒泡直至到达文档根。
DOM事件冒泡的具体流程:第一个接收的是目标元素——第二个接收的是...(子级--父级,按照html结构一层一层往上传) ——然后接收的是body标签——html标签——document对象——最后一个接收的是window对象。
捕获事件流
事件的处理将从 DOM 层次的根开始,而不是从触发事件的目标元素开始, 事件被从目标元素的所有祖先元素依次往下传递。
在这个过程中,事件会被从文档根到事件目标元素之间各个继承派生的元素所捕获,
如果事件监听器在被注册时设置了
useCapture属性为true, 那么它们可以被分派给这期间的任何元素以对事件做出处理; 否则,事件会被接着传递给派生元素路径上的下一元素,直至目标元素。事件到达目标元素后,它会接着通过 DOM 节点再进行冒泡。
DOM 事件捕获的具体流程:捕获是从上到下,具体第一个真正接收的是
window(对象)——第二个接收的是document(对象)—— 第三个接收的是html标签(怎么获取html标签>document.documentElement)——第四个接收的是body(document.body) ——......(父级--子级,剩下的就是按照普通的 html 结构一层一层往下传)——最后到达目标元素。
示例
默认无参数
::: demo [vanilla]
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div
id="div1"
style="color: white;background: red;width: 240px;height: 240px"
>
1
<div
id="div2"
style="color: white;background: green;width: 200px;height: 200px"
>
2
<div
id="div3"
style="color: white;background: blue;width: 150px;height: 150px"
>
3
<div
id="div4"
style="color: white;background: black;width: 100px;height: 100px"
>
4
</div>
</div>
</div>
</div>
<script>
const div1 = document.getElementById('div1');
const div2 = document.getElementById('div2');
const div3 = document.getElementById('div3');
const div4 = document.getElementById('div4');
div1.addEventListener('click', function (e) {
alert('div1的监听事件输出:' + 1);
});
div2.addEventListener('click', function (e) {
alert('div2的监听事件输出:' + 2);
});
div3.addEventListener('click', function (e) {
alert('div3的监听事件输出:' + 3);
});
div4.addEventListener('click', function (e) {
alert('div4的监听事件输出:' + 4);
});
</script>
</body>
</html>
{
"show": false
}
:::
useCapture
tip
与无参数等同
useCapture 指定事件是否在捕获或冒泡阶段执行。
true: 事件在捕获阶段执行false: 默认。事件在冒泡阶段执行
div1.addEventListener(
'click',
function (e) {
alert('div1的监听事件输出:' + 1);
},
false,
);
div2.addEventListener(
'click',
function (e) {
alert('div2的监听事件输出:' + 2);
},
false,
);
div3.addEventListener(
'click',
function (e) {
alert('div3的监听事件输出:' + 3);
},
false,
);
div4.addEventListener(
'click',
function (e) {
alert('div4的监听事件输出:' + 4);
},
false,
);
capture: false
tip
与无参数和 useCapture: false 等同
div1.addEventListener(
'click',
function (e) {
console.log('div1的监听事件输出:', 1);
},
{ capture: false },
);
div2.addEventListener(
'click',
function (e) {
console.log('div2的监听事件输出:', 2);
event.stopPropagation();
},
{ capture: true },
);
div3.addEventListener(
'click',
function (e) {
console.log('div3的监听事件输出:', 3);
},
{ capture: false },
);
div4.addEventListener(
'click',
function (e) {
console.log('div4的监听事件输出:', 4);
},
{ capture: false },
);
阻止事件冒泡或事件捕获
只会有目标元素触发,不会冒泡到上一级元素上 ::: demo [vanilla]
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div
id="div11"
style="color: white;background: red;width: 240px;height: 240px"
>
11
<div
id="div12"
style="color: white;background: green;width: 200px;height: 200px"
>
12
<div
id="div13"
style="color: white;background: blue;width: 150px;height: 150px"
>
13
<div
id="div14"
style="color: white;background: black;width: 100px;height: 100px"
>
14
</div>
</div>
</div>
</div>
<script>
/**
* 当在某一个逻辑中添加 event.stopPropagation(); 会阻止当前的事件向下或向上传播
* 如:在冒泡事件流当中,当我们在 div14 中添加 event.stopPropagation() 时,
* 点击 div14 只会输出 14 因为在 div14 中 event.stopPropagation() 阻止了事件向上传播
*
* 在 div12 中添加。点击div4 会输出 14,13,12 因为在 div12 中事件被组件向上传播
*/
const div11 = document.getElementById('div11');
const div12 = document.getElementById('div12');
const div13 = document.getElementById('div13');
const div14 = document.getElementById('div14');
div11.addEventListener('click', function (e) {
alert('div11的监听事件输出:' + 11);
});
div12.addEventListener('click', function (e) {
e.stopPropagation();
alert('div12的监听事件输出:' + 12);
});
div13.addEventListener('click', function (e) {
alert('div13的监听事件输出:' + 13);
});
div14.addEventListener('click', function (e) {
alert('div14的监听事件输出:' + 14);
e.stopPropagation();
});
</script>
</body>
</html>
{
"show": false
}
:::
捕获事件流示例
useCapture: true
/**
* useCapture 指定事件是否在捕获或冒泡阶段执行。
* true - 事件句柄在捕获阶段执行
* false- false- 默认。事件句柄在冒泡阶段执行
*/
div1.addEventListener(
'click',
function (e) {
console.log('div1的监听事件输出:', 1);
},
true,
);
div2.addEventListener(
'click',
function (e) {
console.log('div2的监听事件输出:', 2);
},
true,
);
div3.addEventListener(
'click',
function (e) {
console.log('div3的监听事件输出:', 3);
},
true,
);
div4.addEventListener(
'click',
function (e) {
console.log('div4的监听事件输出:', 4);
},
true,
);
或者 capture:true
div1.addEventListener(
'click',
function (e) {
console.log('div1的监听事件输出:', 1);
},
{ capture: true },
);
div2.addEventListener(
'click',
function (e) {
console.log('div2的监听事件输出:', 2);
},
{ capture: true },
);
div3.addEventListener(
'click',
function (e) {
console.log('div3的监听事件输出:', 3);
},
{ capture: true },
);
div4.addEventListener(
'click',
function (e) {
console.log('div4的监听事件输出:', 4);
},
{ capture: true },
);
stopImmediatePropagation
event.stopImmediatePropagation 与 event.stopPropagation() 对阻止事件的冒泡与捕获有相同的作用 区别是当给 div2 增加两个 addEventListener click 事件时, 给 div2 的第一个 addEventListener 事件改用 stopImmediatePropagation 时,输出 4,3,2, 而使用 stopPropagation 会输出 4,3,2,22
/**
* event.stopImmediatePropagation 与 event.stopPropagation() 对阻止事件的冒泡与捕获有相同的作用
* 区别是当给 div2 增加两个 addEventListener click 事件时,
* 给 div2 的第一个 addEventListener 事件改用 stopImmediatePropagation 时,输出 4,3,2,
* 而使用 stopPropagation 会输出 4,3,2,22
*/
div1.addEventListener('click', function (e) {
console.log('div1的监听事件输出:', 1);
// event.stopPropagation();
});
div2.addEventListener('click', function (e) {
console.log('div2的监听事件输出:', 2);
// event.stopPropagation();
event.stopImmediatePropagation();
});
div2.addEventListener('click', function (e) {
console.log('div2另一个的监听事件输出:', 22);
});
div3.addEventListener('click', function (e) {
console.log('div3的监听事件输出:', 3);
// event.stopPropagation();
});
div4.addEventListener('click', function (e) {
console.log('div4的监听事件输出:', 4);
// event.stopPropagation();
});
once
表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
/**
* 如下所示给 div1 div2 添加属性 once:true,div3 div4 添加属性 once:false
* @example 当首次点击 div4 由于默认是冒泡事件流,div4 的事件会先触发,接着 div3 div2 div1 依次触发
* 打印 4,3,2,1,然后我们再点击 div4 由于 div1 div2 的 once 为 true
* 所以只会输出 4,3 表示当添加 once 为 true 的 listener 只会触发一次
*/
div1.addEventListener(
'click',
function (e) {
console.log('div1的监听事件输出:', 1);
},
{ once: true },
);
div2.addEventListener(
'click',
function (e) {
console.log('div2的监听事件输出:', 2);
},
{ once: true },
);
div3.addEventListener(
'click',
function (e) {
console.log('div3的监听事件输出:', 3);
},
{ once: false },
);
div4.addEventListener(
'click',
function (e) {
console.log('div4的监听事件输出:', 4);
},
{ once: false },
);
preventDefault & passive
passive: 设置为true时,表示listener永远不会调用preventDefault()。 如果listener仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告event.preventDefault: 告诉浏览器如果此事件没有被显式处理,它默认的动作也不应该照常执行。 此事件还是继续传播,除非碰到事件侦听器调用stopPropagation()或stopImmediatePropagation(),才停止传播。 ::: demo [vanilla]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
这是 checkbox:
<input type="checkbox" id="id-checkbox" />
<br />
这是 a 标签:
<a id="custormUrlOne" onclick="" href="http://www.baidu.com">
跳转百度 event.preventDefault() passive: false
</a>
<br />
这是 a 标签:
<a id="custormUrlTwo" onclick="" href="http://www.baidu.com">
跳转百度 event.preventDefault() passive: true
</a>
<div id="output-box"></div>
<script>
document.querySelector('#id-checkbox').addEventListener(
'click',
function (event) {
document.getElementById('output-box').innerHTML +=
"Sorry! <code>preventDefault()</code> won't let you check this!<br>";
event.preventDefault();
},
false,
);
document.querySelector('#custormUrlOne').addEventListener(
'click',
function (event) {
console.log('点击了 a 标签');
event.preventDefault();
},
{ passive: false },
);
document.querySelector('#custormUrlTwo').addEventListener(
'click',
function (event) {
console.log('点击了 a 标签');
event.preventDefault();
},
{ passive: true },
);
</script>
</body>
</html>
{
"show": true
}
:::

还没有留言,来发表第一条吧~