2024-02-01
前端
00
请注意,本文编写于 325 天前,最后修改于 307 天前,其中某些信息可能已经过时。

目录

事件模型
事件的传播
事件的代理
实例方法
target.addEventListener()
target.removeEventListener()
target.dispatchEvent()

本文主要介绍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,接着依次是documenthtmldocument.documentElement)和bodydocument.body)。

事件的代理

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方式叫做事件的代理(delegation)。

js
var ul = document.querySelector('ul'); ul.addEventListener('click', (event) => { // 判断点击的元素是否是 li 元素,如果是则会进行相应的操作 if (event.target.tagName.toLowerCase() === 'li') { // do something } });

上面代码中,click事件的监听函数定义在<ul>节点,但是实际上,它处理的是子节点<li>click事件。这样做的好处是,只要定义一个监听函数,就能处理多个子节点的事件,而不用在每个<li>节点上定义监听函数。而且以后再添加子节点,监听函数依然有效。

如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation方法。

image.png

js
// 事件传播到 p 元素后,就不再向下捕获了(不影响冒泡阶段) p.addEventListener('click', function (event) { event.stopPropagation(); }, true); // 事件冒泡到 p 元素后,就不再向上冒泡了(不影响捕获阶段) p.addEventListener('click', function (event) { event.stopPropagation(); }, false);

上面代码中,stopPropagation方法分别在捕获阶段和冒泡阶段,阻止了事件的传播。

但是,stopPropagation方法只会阻止事件的传播,不会阻止该事件触发<p>节点的其他click事件的监听函数。也就是说,不是彻底取消click事件。

js
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方法。

js
p.addEventListener('click', function (event) { // 下面两句话不论先后顺序都会执行 event.stopImmediatePropagation(); console.log(1); }); p.addEventListener('click', function(event) { // 不会被触发 console.log(2); });

上面代码中,stopImmediatePropagation方法可以彻底取消这个事件,使得后面绑定的所有click监听函数都不再触发。所以,只会输出1,不会输出2。

实例方法

target.addEventListener()

特点:

  • 该方法可以为针对当前对象的同一个事件,添加多个不同的监听函数。这些函数按照添加顺序触发,即先添加先触发。
  • 绑定相同监听函数前一个会被覆盖,需要注意的是,只有三个参数均相同才算是相同的监听函数。例如:
    js
    target.addEventListener(type, listenerCb [, useCapture]) // 下面会产生两个监听函数,因为第三个参数不同 target.removeEventListener('click', listenerCb, false) target.removeEventListener('click', listenerCb, true)
  • 监听函数内部的this,指向当前事件所在的那个对象。
  • 无返回值

方法入参:

  • type:事件名称,大小写敏感。
  • listenerCb:监听函数。type类型的事件发生时,会触发该监听函数。
  • useCapture:可选参数,布尔值;
    • 默认为false,监听函数只在冒泡阶段目标阶段被触发;
    • 如果设为true,表示监听函数将在捕获阶段目标阶段触发。

注意

  • 第二个参数除了可以是一个函数外,也可以是一个具有handleEvent方法的对象,效果和监听函数相同。

    js
    targetEl.addEventListener('click', { handleEvent: (e) => { console.log('触发了点击事件') } })
  • 第三个参数除了布尔值useCapture,还可以是一个监听器配置对象,定制事件监听行为。该对象有以下属性。

    • capture:布尔值,如果设为true,表示监听函数在捕获阶段触发,默认为false,在冒泡阶段触发。
    • once:布尔值,如果设为true,表示监听函数执行一次就会自动移除,后面将不再监听该事件。该属性默认值为false
    • passive:布尔值,设为true时,表示禁止监听函数调用preventDefault()方法。如果调用了,浏览器将忽略这个要求,并在控制台输出一条警告。该属性默认值为false
    • signal:该属性的值为一个 AbortSignal 对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数。

target.removeEventListener()

该方法用来移除addEventListener()方法添加的事件监听函数。

js
target.addEventListener('click', listenerCb, false);
  • 无返回值
  • 参数与addEventListener()方法完全一致。

需要注意的是,该方法移除的监听函数,必须是addEventListener()方法添加的同一个节点同一个函数同一个触发阶段,即三个参数完全一样才可以,否则是不生效的。

target.dispatchEvent()

该方法在当前节点上触发指定事件,从而触发监听函数的执行。

  • 入参:一个Event对象的实例
  • 返回值:布尔值,只要任意一个监听函数调用了Event.preventDefault(),则返回值为false,否则为true
js
target.dispatchEvent(event)
js
target.addEventListener('click', listenerCb, false); const event = new Event('click'); target.dispatchEvent(event);

上面代码在当前节点触发了click事件。

如果dispatchEvent()方法的参数为空,或者不是一个有效的事件对象,将报错。

下面代码根据dispatchEvent()方法的返回值,判断事件是否被取消了。

js
var canceled = !target.dispatchEvent(event); if (canceled) { console.log('事件取消'); } else { console.log('事件未取消'); }

需要注意的是: 对于不可取消的事件,调用preventDefault()是没有任何效果的,可以通过使用Event.cancelable来检查该事件是否支持取消。

本文作者:xhir

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!