浏览器的 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事件即将触发
相关推荐
angelanana4 天前
【V8引擎blog翻译-191】迭代器助手
浏览器·v8
摇光938 天前
[前端面试]浏览器
前端·面试·职场和发展·浏览器
蓝黑202018 天前
Firefox火狐浏览器打开B站视频时默认静音
firefox·浏览器
cxylay18 天前
【PDF文件】默认被某种软件打开,如何进行修改?
edge·pdf·浏览器·pdf默认打开方式·微软pdf·pdf解决问题
shandianchengzi19 天前
【记录】Windows|Windows 修改字体大全(Windows 桌面、VSCode、浏览器)
windows·vscode·浏览器·字体·美化
金色的暴发户20 天前
从输入url到页面渲染?你还在这么回答吗
前端·面试·浏览器
WangConvey25 天前
Vue检测获取最新资源 解决浏览器缓存问题
javascript·vue.js·缓存·浏览器·浏览器缓存
x-cmd1 个月前
x-cmd pkg | carbonyl - 终端图形浏览器,浏览像素版网页
运维·性能优化·rust·嵌入式·终端·浏览器·命令行
aoi1 个月前
打开新窗口时 debugger 拦截,以查看调用栈排查问题
前端·javascript·浏览器
Amd7941 个月前
Nuxt.js 应用中的 kit:compatibility 事件钩子详解
浏览器·开发·应用·nuxt.js·插件·兼容性·钩子