# DOM 事件
网景和微软最早在浏览器端实现了事件,但 API 互不相同,直到 DOM2 开始标准化 DOM 事件 API。现在的浏览器都是按照 DOM2 Event (opens new window) 实现的。
事件按照触发源可以划分为:浏览器事件、网络事件、用户事件、计时器事件。
# 基本用法和概念
# 注册/绑定事件
addEventListener() (opens new window) 默认会在冒泡阶段注册回调函数,通过 useCapture
参数可以在捕获阶段注册回调函数。
# 事件流:捕获、到达、冒泡
DOM Level 2 Event 规定事件流的阶段为:捕获 (Capturing)、到达、冒泡 (Bubbling)。
关于这点网上许多资料都说的很清楚了,这里就不赘述了。下图是网上找的 (opens new window)。
通过 stopPropagation()
不仅可以阻止冒泡,还可以阻止捕获。冒泡的英文是 bubble,捕获的英文是 capture,而 propatate 是二者的统称。因此许多中文文档将 stopPropagation 解释为阻止冒泡是不准确的。
在实际开发中,如果不能理解时间的冒泡、捕获阶段,则可能产生自己难以理解的 BUG。例如 MDN 上的这个例子 (opens new window)就演示了一个非常简单的业务场景下都会产生的令人费解的 BUG。
# Event 对象
事件处理函数接受的参数就是一个 Event 对象,btn.addEventListener('click', function (evt) { })
。
Event 有很多类别,包括 KeyboardEvent、MouseEvent、TouchEvent 等等。常用的属性有 Event.target
,常用的方法有 Event.preventDefault()
、Event.stopPropagation()
等。完整的列表可以看API 文档 (opens new window)。
# 常用事件
# UIEvent
常用的事件有:load、unload、scroll、resize
# FocusEvent
常用的事件有:focus、blur
# MouseEvent、WheelEvent、DragEvent
MouseEvent 是鼠标事件,常用的有:click、dblclick、mousedown、mouseup、mouseenter、mouseleave、mouseover。
WheelEvent 和 DragEvent 派生自 MouseEvent,分别表示滚轮事件和拖拽事件。
触屏设备上有一些不同:
- 不支持 dblclick 事件,因为双击被用作放大页面
- click 事件会慢 300ms,除非加上
<meta name="viewport" content="width=device-width">
# KeyboardEvent
键盘事件,常用的有:keydown、keyup。
# 最佳实践
# 节流、防抖
一些事件会被高频率触发 (如 mousemove),这样事件处理函数也会被高频调用,非常耗费性能和资源。
一般我们会使用 lodash 库提供的节流防抖接口:_.debounce(func, wait, {leading, trailing, maxWait})
、_.throttle(func, wait, {leading, trailing})
。
手写简易的节流防抖很容易,但功能完备的很难。
应用场景:
- 窗口 resize 事件,需要调用 EChart 之类的库重新绘制,使用防抖
- 搜索框的输入建议,根据需求使用节流防抖都行
- 无限滚动页,页面滚动监听事件,用节流
# 事件委托/代理
如果为每个 DOM 元素都绑定事件回调,那么会很浪费内存。事件委托的关键:首先在顶层元素绑定事件,然后根据 event.target
判断事件的触发元素是否是我们需要处理的。