跳到主要内容

认识

2023年03月03日
柏拉文
越努力,越幸运

一、认识


浏览器事件机制/浏览器事件模型 描述了浏览器如何生成事件、传播事件、处理事件、卸载事件的全过程。其中, 浏览器的事件注册和触发机制确实是发布订阅模式的一个经典实现。过程如下:

一、事件产生: 当用户与页面交互(如点击、输入、滚动等)或系统触发特定行为时,浏览器会生成对应的事件 Event 对象, 并将其作为参数传给事件处理函数, event 对象包含许多有用的属性和方法:

  • 通用属性:

    • type: 事件类型(如 clickkeydown 等)

    • target: 事件最初触发的元素

    • currentTarget: 当前处理事件的元素

    • timeStamp: 事件创建时的时间戳

  • 控制事件流的方法:

    • preventDefault(): 阻止默认行为, 或者 return false 也可以

    • stopPropagation(): 阻止事件继续在捕获或冒泡阶段传播

    • stopImmediatePropagation(): 阻止事件继续传播,并阻止当前元素上的其他相同事件处理程序执行

二、事件注册:

  • DOM Level 0 事件处理程序:

    • HTML 属性方式

      <button onclick="handleClick()">点击</button>
    • DOM 属性方式

      element.onclick = function(event) {
      // 处理事件
      };
  • DOM Level2 事件处理程序: 使用 addEventListener 方法可以在指定的元素上注册事件监听器。该方法允许你指定是否在捕获阶段监听(第三个参数传 true)或在冒泡阶段监听(默认或传 false)。

    element.addEventListener('click', function(event) {
    // 处理事件
    }, useCapture);

三、事件流/事件传播: 浏览器在处理事件时,会依次经过以下三个阶段:

  1. 捕获阶段(Capture Phase, 事件从根节点 window 对象开始,事件沿着 DOM 树向下传递,直到到达目标元素。在这个阶段,如果注册了捕获型事件监听器(通过 addEventListener 的第三个参数设置为 true),这些监听器就会被触发。

  2. 目标阶段(Target Phase, 事件到达触发事件的那个元素(目标元素)时,会在该元素上触发事件。此时,无论监听器是捕获型还是冒泡型,都可能被执行。

  3. 冒泡阶段(Bubbling Phase, 事件在目标元素上触发后,会开始沿着 DOM 树从目标元素向上传递到 window 对象。在这个阶段,注册了冒泡型事件监听器(默认或第三个参数为 false)的元素会被依次触发。注意: 并非所有事件都支持冒泡,例如 focusblur 事件就不支持冒泡。

四、事件处理与控制:

  1. 阻止默认行为, 通过调用 event.preventDefault() 可以阻止事件的默认操作(例如点击链接后的跳转)。

  2. 停止事件传播, 使用 event.stopPropagation() 可以阻止事件在 DOM 树中进一步传播,无论是捕获还是冒泡阶段。

  3. 阻止相同事件传播, stopImmediatePropagation(): 阻止事件继续传播,并阻止当前元素上的其他相同事件处理程序执行

五、事件委托, 事件委托利用冒泡机制,将事件监听器绑定到父元素上,而不需要在每个子元素上都注册事件监听器。这样不仅减少了内存消耗,而且在动态添加或删除子元素时更为高效。

document.getElementById('parent').addEventListener('click', function(event) {
if (event.target.className === 'child') {
// 处理子元素点击事件
}
});

六、自定义事件, 开发者可以创建和分发自定义事件。因此, 我们可以基于某个 DOM 元素来实现发布订阅进行通信。另外, 自定义事件和内置事件在事件流上没有本质区别,因此在用 document.addEventListener 监听自定义事件时,同样可以使用第三个参数, 既可以是布尔值(useCapture)也可以是一个包含更多选项的对象(options)。

// 创建事件
const myEvent = new CustomEvent('myEvent', {
detail: { message: '自定义事件数据' },// 附加的数据,会存储在事件对象的 detail 属性中。通过传递对象、数组或其他数据,可以在事件监听器中获取额外信息。
bubbles: true, // 指定事件是否在 DOM 树中冒泡。如果设置为 true,事件会从触发的目标元素向上层逐级传递;如果为 false,事件只在目标元素上触发。
cancelable: true // 表示该事件是否可以被取消。设置为 true 时,调用 event.preventDefault() 可以阻止事件的默认行为。
});

// 分发事件
element.dispatchEvent(myEvent);

// 监听事件
element.addEventListener('myEvent', function(event) {
console.log(event.detail.message);
});

二、问题


2.1 认识浏览器事件模型?

2.2 认识浏览器事件机制?

2.3 点击 Button 之后, 发生了什么?

浏览器事件机制/浏览器事件模型 的全过程

2.4 e.target 和 e.currentTarget 区别?

e.target 触发事件的元素(指向事件发生的最具体的元素(事件的真正来源))。e.target 可以用来实现事件委托,该原理是通过事件冒泡(或者事件捕获)给父元素添加事件监听

e.currentTarget 当前处理事件的元素(指向绑定事件处理函数的元素)。

2.5 addEventListener 的参数有深入了解吗?

addEventListener(type, listener);
addEventListener(type, listener, options || useCapture);
  • type: 监听事件的类型,如:'click'/'scroll'/'focus'

  • listener: 必须是一个实现了 EventListener 接口的对象,或者是一个函数。当监听的事件类型被触发时,会执行

  • options: 指定 listener 有关的可选参数对象

    • capture: 布尔值,表示 listener 是否在事件捕获阶段传播到 EventTarget 时触发

    • once: 布尔值, 表示 listener 添加之后最多调用一次, 为 truelistener 在执行一次后会移除

    • passive: 布尔值, 表示 listener 永远不会调用 preventDefault()

    • signal: 可选, AbortSignal, 当它的 abort() 方法被调用时,监听器会被移除。用来中断(移除)事件监听器的。

      // 创建 AbortController 实例
      const controller = new AbortController();
      const signal = controller.signal;

      // 使用 signal 添加事件监听器
      element.addEventListener('click', handleClick, { signal });

      // 稍后,当需要移除事件监听器时
      controller.abort(); // 所有使用此 signal 的事件监听器都会被移除
  • useCapture: 布尔值,默认为 false, listener 在事件冒泡阶段结束时执行, true 则表示在捕获阶段开始时执行。作用就是更改事件作用的时机,方便拦截或者不被拦截。