注: 本文作为一篇学习笔记,大部分内容来源于网道。
本文主要介绍addEventListener()方法相关的事件操作内容。
DOM 节点的事件操作(监听和触发),都定义在EventTarget接口。所有节点对象都部署了这个接口。
该接口主要提供三个实例方法。
addEventListener():绑定事件的监听函数removeEventListener():移除事件的监听函数dispatchEvent():触发事件
在讲解具体的实例方法之前,先看一下事件模型。
事件模型
事件的传播
一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播按照顺序分成三个阶段。
graph TB
capture["捕获阶段(capture phase) \n 从 window 对象传导到目标节点,即上层传到底层"] --> target["目标阶段(target phase) \n 在目标节点上触发"] --> bubble["冒泡阶段(bubble phase) \n 从目标节点传导回 window 对象,即从底层传回上层"]
这种三阶段的传播模型,使得同一个事件会在多个节点上触发。
注意,浏览器总是假定click事件的目标节点,就是点击位置嵌套最深的那个节点。
事件传播的最上层对象是window,接着依次是document,html(document.documentElement)和body(document.body)。
事件的代理
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方式叫做事件的代理(delegation)。
var ul = document.querySelector('ul');
ul.addEventListener('click', (event) => {
// 判断点击的元素是否是 li 元素,如果是则会进行相应的操作
if (event.target.tagName.toLowerCase() === 'li') {
// do something
}
});
上面代码中,click事件的监听函数定义在<ul>节点,但是实际上,它处理的是子节点<li>的click事件。这样做的好处是,只要定义一个监听函数,就能处理多个子节点的事件,而不用在每个<li>节点上定义监听函数。而且以后再添加子节点,监听函数依然有效。
如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation方法。
// 事件传播到 p 元素后,就不再向下捕获了(不影响冒泡阶段)
p.addEventListener('click', function (event) {
event.stopPropagation();
}, true);
// 事件冒泡到 p 元素后,就不再向上冒泡了(不影响捕获阶段)
p.addEventListener('click', function (event) {
event.stopPropagation();
}, false);
上面代码中,stopPropagation方法分别在捕获阶段和冒泡阶段,阻止了事件的传播。
但是,stopPropagation方法只会阻止事件的传播,不会阻止该事件触发<p>节点的其他click事件的监听函数。也就是说,不是彻底取消click事件。
p.addEventListener('click', function (event) {
event.stopPropagation();
console.log(1);
});
p.addEventListener('click', function(event) {
// 会触发
console.log(2);
});
上面代码中,p元素绑定了两个click事件的监听函数。stopPropagation方法只能阻止这个事件的传播,不能取消这个事件,因此,第二个监听函数会触发。输出结果会先是1,然后是2。
如果想要彻底取消该事件,不再触发后面所有click的监听函数,可以使用stopImmediatePropagation方法。
p.addEventListener('click', function (event) {
// 下面两句话不论先后顺序都会执行
event.stopImmediatePropagation();
console.log(1);
});
p.addEventListener('click', function(event) {
// 不会被触发
console.log(2);
});
上面代码中,stopImmediatePropagation方法可以彻底取消这个事件,使得后面绑定的所有click监听函数都不再触发。所以,只会输出1,不会输出2。
实例方法
target.addEventListener()
特点:
- 该方法可以为针对当前对象的同一个事件,添加多个不同的监听函数。这些函数按照添加顺序触发,即先添加先触发。
- 绑定相同监听函数前一个会被覆盖,需要注意的是,只有三个参数均相同才算是相同的监听函数。例如:
target.addEventListener(type, listenerCb [, useCapture]) // 下面会产生两个监听函数,因为第三个参数不同 target.removeEventListener('click', listenerCb, false) target.removeEventListener('click', listenerCb, true) - 监听函数内部的
this,指向当前事件所在的那个对象。 - 无返回值
方法入参:
type:事件名称,大小写敏感。listenerCb:监听函数。type类型的事件发生时,会触发该监听函数。useCapture:可选参数,布尔值;- 默认为
false,监听函数只在冒泡阶段和目标阶段被触发; - 如果设为
true,表示监听函数将在捕获阶段和目标阶段触发。
- 默认为
:::note{title="注意"}
-
第二个参数除了可以是一个函数外,也可以是一个具有
handleEvent方法的对象,效果和监听函数相同。targetEl.addEventListener('click', { handleEvent: (e) => { console.log('触发了点击事件') } }) -
第三个参数除了布尔值
useCapture,还可以是一个监听器配置对象,定制事件监听行为。该对象有以下属性。capture:布尔值,如果设为true,表示监听函数在捕获阶段触发,默认为false,在冒泡阶段触发。once:布尔值,如果设为true,表示监听函数执行一次就会自动移除,后面将不再监听该事件。该属性默认值为false。passive:布尔值,设为true时,表示禁止监听函数调用preventDefault()方法。如果调用了,浏览器将忽略这个要求,并在控制台输出一条警告。该属性默认值为false。signal:该属性的值为一个 AbortSignal 对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数。 :::
target.removeEventListener()
该方法用来移除addEventListener()方法添加的事件监听函数。
target.addEventListener('click', listenerCb, false);
- 无返回值
- 参数与
addEventListener()方法完全一致。
需要注意的是,该方法移除的监听函数,必须是addEventListener()方法添加的同一个节点、同一个函数、同一个触发阶段,即三个参数完全一样才可以,否则是不生效的。
target.dispatchEvent()
该方法在当前节点上触发指定事件,从而触发监听函数的执行。
- 入参:一个
Event对象的实例 - 返回值:布尔值,只要任意一个监听函数调用了
Event.preventDefault(),则返回值为false,否则为true。
target.dispatchEvent(event)
target.addEventListener('click', listenerCb, false);
const event = new Event('click');
target.dispatchEvent(event);
上面代码在当前节点触发了click事件。
如果dispatchEvent()方法的参数为空,或者不是一个有效的事件对象,将报错。
下面代码根据dispatchEvent()方法的返回值,判断事件是否被取消了。
var canceled = !target.dispatchEvent(event);
if (canceled) {
console.log('事件取消');
} else {
console.log('事件未取消');
}
需要注意的是: 对于不可取消的事件,调用preventDefault()是没有任何效果的,可以通过使用Event.cancelable来检查该事件是否支持取消。