浏览器的 EventTarget 接口,Event 对象以及原生事件
本文用于个人学习记录,仅供参考
内容参考来源如下:
EventTarget 接口简介
DOM 节点的事件操作(监听和触发),都定义在EventTarget
接口。所有 DOM 节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequest
、AudioNode
、AudioContext
)也部署了这个接口
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;
}
事件监听函数
绑定事件监听函数的方式
-
直接在 DOM 元素里 on- 属性编写事件监听代码
HTML<button onclick="console.log('emit event')">click me</button>
-
js 获取 DOM 元素后,将监听函数赋值给 on- 属性
jsconst buttonEl = document.querySelector('button') buttonEl.onclick = function () { console.log('emit event') }
-
EventTarget.addEventListener
jsbuttonEl.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, touchstartpointerup
: 参考 mouseup, touchendpointermove
: 参考 mousemove, touchmovepointerenter
: 参考 mouseenterpointerover
: 参考 mouseoverpointerout
: 参考 mouseoutpointerleave
:参考 mouseleavepointercancel
: 参考 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
:外部资源停止加载时触发,发生顺序排在error
、abort
、load
等事件的后面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();
拖拉事件开始时,开发者可以提供数据类型和数据值。拖拉过程中,可以通过dragenter
和dragover
事件的监听函数,检查数据类型,以确定是否允许放下(drop)被拖拉的对象。发生drop
事件时,监听函数取出拖拉的数据,对其进行处理
剪切板事件
cut
:将选中的内容从文档中移除,加入剪贴板时触发copy
:进行复制动作时触发paste
:剪贴板内容粘贴到文档后触发
cut
、copy
、paste
这三个事件的事件对象有一个实例属性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
有三个可能的值:loading
:网页正在加载interactive
:网页已经解析完成,但是外部资源仍然处在加载状态complete
:网页和所有外部资源已经结束加载,load
事件即将触发