Skip to content

4-1.事件分类

常见事件大致可总结如下。更多事件分类可详见MDN

4-1-1.资源事件

事件名称何时触发
error资源加载失败时。
abort正在加载资源已经被中止时。
DOMContentLoadedHTML 的加载和处理均完成,DOM 被完全构建完成时。
load资源及其相关资源已完成加载。
beforeunloadwindowdocument 及其资源即将被卸载。
unload文档或一个依赖资源正在被卸载。

4-1-2.网络事件

事件名称何时触发
online浏览器已获得网络访问。
offline浏览器已失去网络访问。

4-1-3.焦点事件

事件名称何时触发
focus元素获得焦点(不会冒泡)。
blur元素失去焦点(不会冒泡

4-1-4.WebSocket事件

事件名称何时触发
openWebSocket 连接已建立。
message通过 WebSocket 接收到一条消息。
errorWebSocket 连接异常被关闭(比如有些数据无法发送)。
closeWebSocket 连接已关闭。

4-1-5.会话历史事件

事件名称何时触发
pagehide页面显示。
pagehide页面隐藏。
popstate页面切换。

4-1-6.CSS动画事件

事件名称何时触发
animationstart某个 CSS 动画开始时触发。
animationend某个 CSS 动画完成时触发。
animationiteration某个 CSS 动画完成后重新开始时触发。

4-1-7.CSS 过渡事件

事件名称何时触发
transitionstartCSS 过渡开始。
transitioncancelCSS 过渡取消。
transitionendCSS 过渡结束。
transitionrunCSS 过渡开始运行(在transitionstart之前执行)。

4-1-8.表单事件

事件名称何时触发
reset点击重置按钮时
submit点击提交按钮

4-1-9.打印事件

事件名称何时触发
beforeprint打印机已经就绪时触发。
afterprint打印机关闭时触发。

4-1-10.文本写作事件

该类事件可用于检测中文输入法的文本拼音合成。常用于 input 输入框。

事件名称何时触发
compositionstart开始
compositionupdate更新
compositionend结束

4-1-11.视图事件

事件名称何时触发
fullscreenchange元素由正常展示转为全屏展示或者由全屏展示转为正常展示
fullscreenerror转为全屏展示时出错(可能是技术原因,也可能是权限被拒绝)
resizeThe document view has been resized.
scrollThe document view or an element has been scrolled.

4-1-12.剪贴板事件

事件名称何时触发
cut已经剪贴选中的文本内容并且复制到了剪贴板。即 ctrl + xcommand + x
copy已经把选中的文本内容复制到了剪贴板。即 ctrl + ccommand + c
paste从剪贴板复制的文本内容被粘贴。即 ctrl + vcommand + v

4-1-13.键盘事件

事件名称何时触发
keydown按下任意按键。
keypressShiftFnCapsLock 外的任意键被按住。(连续触发。)
keyup释放任意按键。

4-1-14.鼠标事件

事件名称何时触发
click在元素上按下并释放任意鼠标按键。
contextmenu右键点击(在右键菜单显示前触发)。
dblclick在元素上双击鼠标按钮。
mousedown在元素上按下任意鼠标按钮。
mouseenter指针移到有事件监听的元素内。
mouseleave指针移出元素范围外(不冒泡)。
mousemove指针在元素内移动时持续触发。
mouseover指针移到有事件监听的元素或者它的子元素内。
mouseout指针移出元素,或者移到它的子元素上。
mouseup在元素上释放任意鼠标按键。
pointerlockchange鼠标被锁定或者解除锁定发生时。
pointlockerror可能因为一些技术的原因鼠标锁定被禁止时。
select有文本被选中。
wheel滚轮向任意方向滚动。

4-1-15.拖放事件

事件名称何时触发
drag正在拖动元素或文本选区(在此过程中持续触发,每350ms触发一次)。
dragend拖放操作结束(松开鼠标按钮或按下Esc键)。
dragenter被拖动的元素或文本选区移入有效释放目标区。
dragstart用户开始拖动HTML元素或选中的文本。
dragleave被拖动的元素或文本选区移出有效释放目标区。
dragover被拖动的元素或文本选区正在有效释放目标上被拖动 (在此过程中持续触发,每350ms触发一次)。
drop元素在有效释放目标区上释放。
                            |

4-2.事件处理程序

我们可以给事件添加事件处理程序,以在事件被触发时,做出对应响应。

添加方式有三种:

4-2-1.HTML 特性

处理程序可以设置在 HTML 中名为 on<event> 的特性(attribute)中。

譬如我们添加一个点击事件:

html
<!-- !!!这里是handleClick() 而不是handleClick 代表事件触发后,执行该函数 -->
<button onclick="handleClick()">click</button>
<script>
  function handleClick () {
    alert('The element is clicked!')
  }
</script>

TIP

HTML 特性名是大小写不敏感的,所以 ONCLICKonClick 以及 onCLICK 都一样可以运行。

但是特性通常是小写的:onclick

4-2-2.DOM 属性

还可以使用 DOM 属性(propertyon<event> 来分配处理程序。

html
<button id="btn" onclick="handleClick()">click</button>
<script>
  function handleClick () {
    alert('The element is clicked!')
  }
  // 该方式会覆盖HTML的onclick特性
  btn.onclick = function () { // 另外,这里绑定的是函数体,而不是函数执行结果。这与HTML的onclick特性的写法有所不同。
    alert('The button is clicked!')
  }
</script>

然后,HTML 特性或者 DOM 属性这两种方式,都有缺点。它们都不能绑定同一个事件类型的多个事件处理程序

html
<button id="btn">click</button>
<script>
  btn.onclick = function () {
    alert('The element is clicked!')
  }
  // 多次绑定click事件处理程序 该处理程序会覆盖上面的处理程序
  btn.onclick = function () {
    alert('The button is clicked!')
  }
</script>

于是 w3c 提出了新的解决方案,即 element.addEventListener()

4-2-3.addEventListener

addEventListener 可用于给目标 DOM 元素添加处理程序,其语法如下:

element.addEventListener(eventType, eventCallback, options)

  1. eventType: 事件类型。譬如 clickfocusmouseover 等。
  2. eventCallback: 事件处理程序。
  3. options: 配置参数。有两种形式布尔值或者对象。不同形式有不同的配置语义。

options 为布尔值时,代表 useCapture,其值有以下:

  • false: 默认值。代表处理程序在事件冒泡阶段触发。
  • true: 代表处理程序在事件捕获阶段触发。

options 为对象时,其可配置参数如下:

  • capture: 与 useCapture 一致。默认值 false,代表处理程序在事件冒泡阶段触发。true 代表处理程序在事件捕获阶段触发。
  • once: 表示处理程序是否只触发一次。
  • passive: 表示处理程序是否要触发 event.preventDefault() 。设置为 true 时,事件处理程序内部的 event.preventDefault() 将不起作用。另外部分事件 scroll mousemove 等,为了更好的性能体验,passive 默认为 true

使用 addEventListener 能够给同一事件类型添加多个事件处理程序:

html
<button id="btn">click</button>
<script>
  var btn = document.querySelector('#btn')
  btn.addEventListener('click', function () {
    alert('The element is clicked!')
  })

  btn.addEventListener('click', function () {
    alert('The button is clicked!')
  })
</script>

另外,为了防止内存泄漏,在页面销毁时或者事件需要失效时,可以主动使用 removeEventListener 移除某一事件的处理程序。

html
<button id="target">click me</button>
<button id="remove">remove</button>
<script>
  var target = document.querySelector('#target')
  var remove = document.querySelector('#remove')
  // 给target添加上事件处理程序
  target.addEventListener('click', function () {
    alert('The element is clicked!')
  })
  var handler = function () {
    alert('The button is clicked!')
  }
  target.addEventListener('click', handler)
  // 点击remove按钮时 移除target上的事件处理程序
  remove.addEventListener('click', function () {
    target.removeEventListener('click', handler)
  })
</script>

4-3.冒泡与捕获

前文提到了 addEventListener 可配置 useCapturecapture 来决定事件的触发阶段是在事件冒泡还是事件捕获。

事件冒泡是 IE 提出的浏览器标准,事件捕获则是 NetScape 提出的浏览器标准。

我们先看下事件冒泡

html
<style>
  .box {
    margin: 10px;
    border: 1px solid skyblue;
    cursor: pointer;
  }
</style>
<!-- 事件冒泡 -->
<div id="top1" class="box">Top
  <div id="middle1" class="box">Middle
    <p id="bottom1" class="box">Bottom</p>
  </div>
</div>
<script>
  var top = document.querySelector('#top1')
  var middle = document.querySelector('#middle1')
  var bottom = document.querySelector('#bottom1')
  top.addEventListener('click', function (e) {
    alert('Top')
  })
  middle.addEventListener('click', function (e) {
    alert('Middle')
  })
  bottom.addEventListener('click', function (e) {
    alert('Bottom')
  })
</script>

当点击 Bottom 元素时,会依次弹出 BottomMiddleTop

因为事件会默认在冒泡阶段触发。

下面看一下事件捕获

html
<!-- 事件捕获 -->
<div id="top2" class="box">Top
  <div id="middle2" class="box">Middle
    <p id="bottom2" class="box">Bottom</p>
  </div>
</div>
<script>
  var top = document.querySelector('#top2')
  var middle = document.querySelector('#middle2')
  var bottom = document.querySelector('#bottom2')
  top.addEventListener('click', function (e) {
    alert('Top')
  }, {
    capture: true
  })
  middle.addEventListener('click', function (e) {
    alert('Middle')
  }, {
    capture: true
  })
  bottom.addEventListener('click', function (e) {
    alert('Bottom')
  }, {
    capture: true
  })
</script>

当点击 Bottom 元素时,会依次弹出 TopMiddleBottom

因为事件会默认在捕获阶段触发。

然而下面的例子,在点击 Bottom 元素后,则是依次弹出 MiddleBottomTop

html
<div id="top3" class="box">Top
  <div id="middle3" class="box">Middle
    <p id="bottom3" class="box">Bottom</p>
  </div>
</div>
<script>
  var top = document.querySelector('#top3')
  var middle = document.querySelector('#middle3')
  var bottom = document.querySelector('#bottom3')
  top.addEventListener('click', function (e) {
    alert('Top')
  })
  middle.addEventListener('click', function (e) {
    alert('Middle')
  }, {
    capture: true
  })
  bottom.addEventListener('click', function (e) {
    alert('Bottom')
  })
</script>

这是为了兼容两套标准事件冒泡事件捕获

W3C 规定事件流先执行事件捕获阶段,然后再执行事件冒泡阶段。

4-4.DOM事件流

首先我们感受下 DOM 事件流:

html
<div id="top4" class="box">Top
  <div id="middle4" class="box">Middle
    <p id="bottom4" class="box">Bottom</p>
  </div>
</div>
<script>
  var top = document.querySelector('#top4')
  var middle = document.querySelector('#middle4')
  var bottom = document.querySelector('#bottom4')
  top.addEventListener('click', function (e) {
    alert('Top')
  })
  top.addEventListener('click', function (e) {
    alert('Top-capture')
  }, {
    capture: true
  })
  middle.addEventListener('click', function (e) {
    alert('Middle')
  })
  middle.addEventListener('click', function (e) {
    alert('Middle-capture')
  }, {
    capture: true
  })
  bottom.addEventListener('click', function (e) {
    alert('Bottom')
  })
  bottom.addEventListener('click', function (e) {
    alert('Bottom-capture')
  }, {
    capture: true
  })
</script>

上述代码分别给元素添加上了冒泡事件和捕获事件。

在点击 Bottom 元素后,会依次弹出 Top-captureMiddle-captureBottom-captureTopMiddleBottom

TIP

要说明的一点,事件流始终是按照事件捕获 => 事件冒泡这样的方式进行的。

addEventListeneruseCapturecapture 参数只是声明事件在哪个事件流阶段触发,并不会影响到事件流的默认执行。

如果想要阻止事件冒泡或者事件捕获的话,可以使用 event.stopPropagation()

4-5.浏览器默认行为

许多事件会自动触发浏览器执行某些默认行为。譬如:

  • 点击一个链接 —— 触发导航(navigation)到该 URL
  • 点击表单的提交按钮 —— 触发提交到服务器的行为。

4-5-1.阻止默认行为

有两种方式来告诉浏览器我们不希望它执行默认行为:

  1. 主流的方式是使用 event 对象。有一个 event.preventDefault() 方法。
  2. 如果处理程序是使用 on<event>(而不是 addEventListener)分配的,那返回 false 也同样有效。
html
<a href="/" onclick="return false">return false</a>

<a href="/" onclick="event.preventDefault()">preventDefault</a>

return false

preventDefault

4-5-2.passive

addEventListenerpassive 参数将阻止处理程序调用 event.preventDefault()

html
<a href="/" id="passive-href">passive-href</a>
<script>
  var passive = document.querySelector('#passive-href')
  passive.addEventListener('click', function (event) {
    event.preventDefault()
  }, {
    passive: true
  })
</script>

passive-href

上述链接即使已经添加了 event.preventDefault(),但在点击之后依然会跳转。

根据规范,passive 选项的默认值始终为 false。但是,这引入了处理某些触摸事件(以及其他)的事件监听器在尝试处理滚动时阻止浏览器的主线程的可能性,从而导致滚动处理期间性能可能大大降低。

为防止出现此问题,某些浏览器(特别是 ChromeFirefox)已将文档级节点 WindowDocumentDocument.bodytouchstarttouchmove 事件的 passive 选项的默认值更改为 true

这可以防止调用事件监听器,因此在用户滚动时无法阻止页面呈现。

添加 passive 参数后,touchmove 事件不会阻塞页面的滚动(同样适用于鼠标的滚轮事件)。在这里查看demo(需要翻墙)。

更多详细信息可见MDN

4-5-3.defaultPrevented

defaultPrevented 属性可以用来判断事件流阶段(事件冒泡事件捕获)是否调用了 event.preventDefault()

譬如下例,我们通常如果想要阻止事件冒泡触发父元素的事件时,通常会使用 e.stopPropagation()

html
<div id="box">Box
  <a href="/" id="href">click</a>
</div>
<script>
  var box = document.querySelector('#box')
  var href = document.querySelector('#href')
  box.addEventListener('click', function () {
    alert('box')
  })
  href.addEventListener('click', function (e) {
    e.preventDefault()
    // 阻止传播 
    e.stopPropagation()
    alert('href')
  })
</script>

当然,这样能够实现我们想要的效果。但由于事件委托的优势,一般情况下不推荐使用 stopPropagation 方法。

我们可以使用 defaultPrevented 属性来判断:

html
<div id="box">Box
  <a href="/" id="href">click</a>
</div>
<script>
  var box = document.querySelector('#box')
  var href = document.querySelector('#href')
  box.addEventListener('click', function (e) {
    if (e.defaultPrevented) return
    alert('box')
  })
  href.addEventListener('click', function (e) {
    e.preventDefault()
    alert('href')
  })
</script>

4-6.自定义事件

我们不但可以利用 JavaScript 来创建自定义事件,甚至也可以创建 clickmousemove 等浏览器的内置事件。

4-6-1.Event

new Event 是创建自定义事件的基础用法。

new Event(type[, options])

  • type: 自定义事件名。
  • options: 相关配置。有以下两个参数:
    1. bubbles: 事件能否冒泡。默认值为 false
    2. cancelable: 事件能否阻止默认浏览器行为。该属性配合 dispatchEvent 有相关用法。默认值为 false

另外W3C为了更好的区分事件类别,还额外添加许多事件类别:

  • MouseEvent
  • WheelEvent
  • KeyboardEvent
  • ...

相对 Event 来说,上述事件类别支持更多针对每个事件的详细参数配置。

4-6-2.CustomEvent

此外,还有一个特殊的事件类别 CustomEvent

它相对 Event 有一个额外的 detail 属性:

js
var customEvent = new CustomEvent('greet', {
  bubbles: true,
  cancelable: true,
  detail: {
    tip: '自定义事件'
  }
})
console.log(customEvent)

4-6-3.dispatchEvent

在创建自定义事件之后,可以利用 element.dispatchEvent(event) 来触发事件。

然后,如果该自定义事件绑定了事件处理程序,那么浏览器会对它做出反应

如果事件是用 bubbles 标志创建的,那么它会冒泡。

如果事件是用 cancelable 标志创建的,那么它可以阻止默认行为。

html
<a href="/" onclick="alert('click')" id="dispatch-a">测试a链接</a>
<button id="dispatch-button">dispatchEvent</button>
<script>
  var target = document.querySelector('#dispatch-a')
  var button = document.querySelector('#dispatch-button')
  var clickEvent = new Event('click', {
    bubbles: false,
    cancelable: true
  })
  // 绑定事件处理程序
  target.addEventListener('click', function (e) {
    alert('ClickEvent is triggered')
    // 阻止a链接默认行为
    e.preventDefault()
  })
  // 主动触发
  button.addEventListener('click', function () {
    // dispatchEvent的返回值与cancelable、e.preventDefault()有关联
    var result = target.dispatchEvent(clickEvent)
    console.log(result) // false
  })
</script>

测试a链接

TIP

dispatchEvent 触发的事件,event.isTrusted 属性为 false

而由用户操作触发的事件,event.isTrusted 属性为 true

可根据该属性来判断事件的产生来源。

而关于 dispatchEvent 的返回值与 cancelablee.preventDefault() 的联系可以总结如下:

  • cancelabletrue, 而且事件处理程序中调用了 e.preventDefault(),此时 dispatchEvent 的返回值为 false
  • 上述条件有一条不满足,则dispatchEvent 的返回值为 true

该特性可以用来判断自定义事件是否调用了 e.preventDefault()

TIP

另外,dispatchEvent 的执行是同步的。