浏览器的 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事件即将触发