浏览器的 EventTarget 接口,Event 对象以及原生事件

浏览器的 EventTarget 接口,Event 对象以及原生事件

本文用于个人学习记录,仅供参考

内容参考来源如下:

  1. 事件 - JavaScript 教程 - 网道 (wangdoc.com)
  2. Event - Web API 接口参考 | MDN (mozilla.org)

EventTarget 接口简介

DOM 节点的事件操作(监听和触发),都定义在EventTarget接口。所有 DOM 节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequestAudioNodeAudioContext)也部署了这个接口

vscode 内置的 lib.dom.d.ts 文件中 EventTarget 的类型如下:

ts 复制代码
interface EventTarget {
  /**
   * 用于绑定事件监听函数
   * type: 事件名称,大小写敏感
   * callback: 监听器函数或对象,通常只传函数
   * options: 事件传播方式,一个配置对象或布尔值,布尔值即配置对象中的 capture 的属性值,默认为 false
   */
  addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
  /* 触发事件 */
  dispatchEvent(event: Event): boolean;
  /* 用于移除事件监听函数,参数需要与 addEventListener 绑定的一致才能移除 */
  removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
}

监听器函数或对象的类型:

ts 复制代码
interface EventListener {
  (evt: Event): void;
}
interface EventListenerObject {
  handleEvent(object: Event): void;
}
type EventListenerOrEventListenerObject = EventListener | EventListenerObject;

事件传播方式配置对象的类型:

ts 复制代码
interface EventListenerOptions {
  /* true 表示监听函数在捕获阶段触发,默认为 false,在冒泡阶段触发 */
  capture?: boolean;
}
interface AddEventListenerOptions extends EventListenerOptions {
  /* true 表示监听函数只执行一次便移除。默认为 false */
  once?: boolean;
  /* true 表示禁止监听函数调用 */
  passive?: boolean;
  /* 该属性的值为一个 AbortSignal 对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数 */
  signal?: AbortSignal;
}

事件监听函数

绑定事件监听函数的方式

  1. 直接在 DOM 元素里 on- 属性编写事件监听代码

    HTML 复制代码
    <button onclick="console.log('emit event')">click me</button>
  2. js 获取 DOM 元素后,将监听函数赋值给 on- 属性

    js 复制代码
    const buttonEl = document.querySelector('button')
    buttonEl.onclick = function () {
      console.log('emit event')
    }
  3. EventTarget.addEventListener

    js 复制代码
    buttonEl.addEventListener('click', function () {
      console.log('emit event')
    })

推荐使用 EventTarget.addEventListener,它有如下优点:

  • 可为同一个事件添加多个监听函数
  • 可配置事件传播方式

事件监听函数的 this 指向

如果监听函数不是箭头函数,监听函数内部的 this 指向触发事件的那个元素节点。箭头函数的 this 则由其声明时的词法环境决定

事件传播

一个事件发生后,默认会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段

  • 第一阶段:从window对象传导到目标节点(上层传到底层),即 捕获阶段(capture phase)
  • 第二阶段:在目标节点上触发,即 目标阶段(target phase)
  • 第三阶段:从目标节点传导回window对象(从底层传回上层),即 冒泡阶段(bubbling phase)

事件默认在冒泡阶段触发,可通过更改 addEventListener 方法的第三个参数来决定事件在捕获阶段触发还是在冒泡阶段触发

这种三阶段的传播模型,使得同一个事件会在多个节点上触发。必要时可以通过事件对象的 stopPropagation 方法阻止事件传播

js 复制代码
p.addEventListener('click', function (event) {
  // 事件传播到 p 元素后,就不再向下传播了
  event.stopPropagation()
}, true)

p.addEventListener('click', function (event) {
  // 事件冒泡到 p 元素后,就不再向上冒泡了
  event.stopPropagation()
}, false)

事件代理

利用事件传播的特性,可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫做事件的代理(delegation)

事件代理的优点是不需要在每个子节点上都绑定监听函数

js 复制代码
const ul = document.querySelector('ul')

ul.addEventListener('click', function (event) {
  if (event.target.tagName.toLowerCase() === 'li') {
    // some code
  }
});

Event 对象概述

事件发生以后,会产生一个事件对象,作为参数传给监听函数。浏览器原生提供一个 Event 对象,所有的事件都是这个对象的实例

vscode 内置的 lib.dom.d.ts 文件中有 Event 对象的声明

ts 复制代码
// 这里只给出比较常用的几个属性和方法
interface Event {
  /* 事件名称 */
  readonly type: string;
  /* 事件当前注册的元素 */
  readonly currentTarget: EventTarget | null;
  /* 最初分发事件的元素 */
  readonly target: EventTarget | null;
  /* 取消事件(如果该事件可取消) */
  preventDefault(): void;
  /* 阻止事件传播 */
  stopPropagation(): void;
  // ...
}

interface EventInit {
  /* 事件对象是否冒泡,默认 false */
  bubbles?: boolean;
  /* 事件是否可以被 Event.preventDefault() 取消, 默认 false */
  cancelable?: boolean;
  /* 事件是否可以穿过 Shadow DOM 和常规 DOM 之间的边界进行冒泡, 默认 false */
  composed?: boolean;
}

declare var Event: {
  prototype: Event;
  /**
   * Event 构造函数 
   * type 事件名称
   * eventInitDict 事件初始化配置
   */
  new(type: string, eventInitDict?: EventInit): Event;
  readonly NONE: 0;
  readonly CAPTURING_PHASE: 1;
  readonly AT_TARGET: 2;
  readonly BUBBLING_PHASE: 3;
};

自定义事件

既然浏览器原生提供了 Event 构造函数,那我们自然可以创建和分派 DOM 事件,这些事件通常称为合成事件

js 复制代码
const evt = new Event(
  'look',
  {
    'bubbles': true,
    'cancelable': false
  }
)

document.addEventListener('look', () => console.log('look event emit'))
document.dispatchEvent(evt)

浏览器原生事件概述

这里基本只有事件的类型的简单介绍,具体内容可参照 MDN,WangDoc 之类的文档

鼠标事件

(1)点击事件

  • click:按下鼠标(通常是按下主按钮)时触发
  • dblclick:在同一个元素上双击鼠标时触发
  • mousedown:按下鼠标键时触发
  • mouseup:释放按下的鼠标键时触发

点击事件的触发顺序为: mousedown -> mouseup -> click -> dbclick

(2)移动事件

  • mousemove:当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次
  • mouseenter:鼠标进入一个节点时触发,进入子节点不会触发这个事件
  • mouseover:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件
  • mouseout:鼠标离开一个节点时触发,离开父节点也会触发这个事件
  • mouseleave:鼠标离开一个节点时触发,离开父节点不会触发这个事件

触摸事件

  • touchstart:用户开始触摸时触发
  • touchend:用户不再接触触摸屏时(或者移出屏幕边缘时)触发
  • touchmove:用户移动触摸点时触发
  • touchcancel:触摸点取消时触发,比如在触摸区域跳出一个模态窗口(modal window)、触摸点离开了文档区域(进入浏览器菜单栏)、用户的触摸点太多,超过了支持的上限(自动取消早先的触摸点)

指针事件

指针事件是一类可以为定点设备所触发的 DOM 事件。它们被用来创建一个可以有效掌握各类输入设备(鼠标、触控笔和单点或多点的手指触摸)的统一的 DOM 事件模型

一个指针事件,也同时包含了鼠标事件中的属性以及适用于其他输入设备的新属性

主要包含以下事件:

  • pointerdown: 参考 mousedown, touchstart
  • pointerup: 参考 mouseup, touchend
  • pointermove: 参考 mousemove, touchmove
  • pointerenter: 参考 mouseenter
  • pointerover: 参考 mouseover
  • pointerout: 参考 mouseout
  • pointerleave:参考 mouseleave
  • pointercancel: 参考 touchcancel

键盘事件

事件类型:

  • keydown:按下键盘时触发
  • keypress:按下有值的键时触发,即按下 Ctrl、Alt、Shift、Meta 这样无值的键,这个事件不会触发。对于有值的键,按下时先触发keydown事件,再触发这个事件
  • keyup:松开键盘时触发该事件

如果用户一直按键不松开,就会连续循环触发 keydown 与 keypress

表单事件

  • input: 当<input><select><textarea>的值发生变化时触发,对于复选框(<input type=checkbox>)或单选框(<input type=radio>),用户改变选项时,也会触发这个事件。另外,对于打开contenteditable属性的元素,只要值发生变化,也会触发input事件
  • select:在<input><textarea>里面选中文本时触发
  • change:当<input><select><textarea>的值发生变化时触发。它与input事件的最大不同,就是不会连续触发,只有当全部修改完成时才会触发
  • invalid:用户提交表单时,如果表单元素的值不满足校验条件,就会触发invalid事件
  • reset:当表单重置(所有表单成员变回默认值)时触发
  • submit:当表单数据向服务器提交时触发

进度事件

进度事件用来描述资源加载的进度,主要由 AJAX 请求、<img><audio><video><style><link>等外部资源的加载触发,主要包含以下事件:

  • abort:外部资源中止加载时(比如用户取消)触发。如果发生错误导致中止,不会触发该事件
  • error:由于错误导致外部资源无法加载时触发
  • load:外部资源加载成功时触发
  • loadstart:外部资源开始加载时触发
  • loadend:外部资源停止加载时触发,发生顺序排在errorabortload等事件的后面
  • progress:外部资源加载过程中不断触发
  • timeout:加载超时时触发

除了资源加载,文件上传也存在这些事件

拖拉事件

在网页中,一个DOM元素能否被拖拉取决与其draggable属性,绝大部分DOM元素的draggable属性值都为 false,即不可拖拉,<img><a>默认为 true

一旦某个元素节点的draggable属性设为true,就无法再用鼠标选中该节点内部的文字或子节点

拖拉事件有以下几种:

  • dragstart:用户开始拖拉时,在被拖拉的节点上触发
  • drag:拖拉过程中,在被拖拉的节点上持续触发(相隔几百毫秒)
  • dragend:拖拉结束时被拖拉的节点上触发
  • dragenter:拖拉进入当前节点时,在当前节点上触发
  • dragover:拖拉到当前节点上方时,在当前节点上持续触发
  • dragleave:拖拉操作离开当前节点范围时,在当前节点上触发
  • drop:被拖拉的节点或选中的文本,释放到目标节点时,在目标节点上触发。如果当前节点不允许drop,即使在该节点上方松开鼠标键,也不会触发该事件。如果用户按下 ESC 键,取消这个操作,也不会触发该事件

所有拖拉事件的事件对象都有一个dataTransfer属性,用来读写需要传递的数据。这个属性的值是一个DataTransfer实例

js 复制代码
const dataTrans = new DataTransfer();

拖拉事件开始时,开发者可以提供数据类型和数据值。拖拉过程中,可以通过dragenterdragover事件的监听函数,检查数据类型,以确定是否允许放下(drop)被拖拉的对象。发生drop事件时,监听函数取出拖拉的数据,对其进行处理

剪切板事件

  • cut:将选中的内容从文档中移除,加入剪贴板时触发
  • copy:进行复制动作时触发
  • paste:剪贴板内容粘贴到文档后触发

cutcopypaste这三个事件的事件对象有一个实例属性clipboardData,与拖拉事件对象的 dataTransfer 属性一样,是一个 DataTransfer 实例,用于存放剪贴的数据

窗口事件

  • scroll: 在文档或文档元素滚动时触发,该事件会连续触发
  • resize: 窗口大小时触发,主要发生在window对象上面
  • fullscreenchange: 进入或退出全屏状态时触发,该事件发生在document对象上面
  • fullscreenerror: 进入或退出全屏状态时触发,该事件发生在document对象上面

焦点事件

  • focus:元素节点获得焦点后触发,该事件不会冒泡
  • blur:元素节点失去焦点后触发,该事件不会冒泡
  • focusin:元素节点将要获得焦点时触发,发生在focus事件之前。该事件会冒泡
  • focusout:元素节点将要失去焦点时触发,发生在blur事件之前。该事件会冒泡

session 历史事件

  • pageshow:页面加载时触发,包括第一次加载和从缓存加载两种情况,可通过事件对象的 persisted 属性分辨,true 表示从缓存中加载
  • pagehide:用户通过"前进/后退"按钮,离开当前页面时触发,可通过设置事件对象的 persisted 属性来决定是否要缓存当前页面
  • popstate:浏览器的history对象的当前记录发生显式切换时触发。注意,调用history.pushState()history.replaceState(),并不会触发popstate事件。该事件只在用户在history记录之间显式切换时触发,比如鼠标点击"后退/前进"按钮,或者在脚本中调用history.back()history.forward()history.go()时触发
  • hashchange:URL 的 hash 部分(即#号后面的部分,包括#号)发生变化时触发。该事件一般在window对象上监听

网页状态事件

  • DOMContentLoaded: 网页下载并解析完成以后,浏览器就会在document对象上触发。这时,仅仅完成了网页的解析(整张页面的 DOM 生成了),所有外部资源(样式表、脚本、iframe 等等)可能还没有下载结束

  • readystatechange: 当 Document 对象和 XMLHttpRequest 对象的readyState属性发生变化时触发

    document.readyState有三个可能的值:

    1. loading:网页正在加载
    2. interactive:网页已经解析完成,但是外部资源仍然处在加载状态
    3. complete:网页和所有外部资源已经结束加载,load事件即将触发
相关推荐
魔云连洲13 小时前
浏览器强缓存还未过期,但服务器资源已经变了怎么办?
前端·缓存·浏览器
打小就很皮...19 天前
浏览器存储 Cookie,Local Storage和Session Storage
前端·缓存·浏览器
小妖66621 天前
chrome 浏览器怎么不自动提示是否翻译网站
浏览器
大名人儿24 天前
【浏览器网络请求全过程】
浏览器·网络请求·详解·全过程
windliang1 个月前
Cursor 写一个网页标题重命名的浏览器插件
前端·浏览器
前端付豪1 个月前
1、为什么浏览器要有渲染流程? ——带你一口气吃透 Critical Rendering Path
前端·后端·浏览器
啵啵学习1 个月前
浏览器插件,提示:此扩展程序未遵循 Chrome 扩展程序的最佳实践,因此已无法再使用
前端·chrome·浏览器·插件·破解
前端南玖1 个月前
通过performance面板验证浏览器资源加载与渲染机制
前端·面试·浏览器
mx9511 个月前
真实业务场景:在React中使用Web Worker实现HTML导出PDF的性能优化实践
性能优化·浏览器
前端南玖1 个月前
浏览器如何确定最终的CSS属性值?解析计算优先级与规则
前端·css·浏览器