快速了解JavaScript的事件

概述

JavaScript 与 HTML 之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。这种在传统软件工程中被称为观察员模式的模型,支持页面的行为(JavaScript 代码)与页面的外观(HTML 和 CSS 代码)之间的松耦合。

事件流

事件流 描述的是从页面中接收事件的顺序。IE 和 Netscape 开发团队提出了两种实现事件流的方案,事件冒泡流和事件捕获流。

IE 提出的方案为事件冒泡流,从最具体的元素开始触发,然后向上传播至文档节点。

image

Netscape 团队提出的方案为事件捕获流,事件捕获由文档节点开始传播至最具体的元素。事件捕获实际上是为在事件到达最终目标前拦截事件。

image

DOM 事件流

DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。

  1. 事件捕获阶段:事件从上往下查找对应元素,直到捕获到事件。
  2. 到达目标阶段:到达目标元素后执行事件对应的处理函数。
  3. 事件冒泡阶段:事件从目标元素开始冒泡。

image

在 DOM 事件流中,实际的目标(

元素)在捕获阶段不会收到事件。这是因为捕获阶段从document再到就结束了。下一阶段,即会在
元素上触发事件的"到达目标"阶段,通常在事件处理时被认为是冒泡阶段的一部分。然后,冒泡阶段开始,事件反向传播至文档。

事件处理

事件就是在浏览器上执行的某种动作。如clickload等。事件处理程序(或事件监听器)就是响应事件而调用的函数。

HTML事件处理程序

可以在支持事件的HTML元素上通过属性来指定能够执行的JavaScript代码的值。如下所示:

在 HTML 中定义的事件处理程序可以包含精确的动作指令,也可以调用在页面其他地方定义的脚本:


在调用handleHTMLEvent()函数之前点击了按钮,会发生错误。可以将HTML事件处理程序封装在try/catch块中:

这种事件处理程序会使HTML与JavaScript强耦合,不利于维护。

DOM0事件处理程序

通过JavaScript获取事件处理程序并将一个函数赋值给该事件处理程序属性。


使用DOM0方式为事件处理程序赋值的函数被认为是元素的方法。此时获取的this等于事件处理程序所在的元素。以这种方式添加事件处理程序注册在事件流的冒泡阶段。

DOM2事件处理程序

DOM2 Events 定义addEventListener()removeEventListener() 两个方法,用于事件处理程序的赋值和移除。所有DOM节点都含这两个方法,它们接收 3 个参数:事件名、事件处理函数和一个布尔值,布尔值表示调用事件处理程序的事件流阶段,true在捕获阶段,false(默认)在冒泡阶段。


使用addEventListener()可以为同一个事件添加多个事件处理程序。

var btn = document.getElementById('btn');
btn.addEventListener("click", () => {
    alert("DOM2 Event Handler");
}, false);
btn.addEventListener("click", () => {
    alert("Rep DOM2 Event Handler");
}, false);

使用removeEventListener()并传入与addEventListener()同样的参数来移除。

var btn = document.getElementById('btn');
btn.addEventListener("click", () => {
    alert("DOM2 Event Handler");
}, false);
btn.removeEventListener("click", function () {    // 无效果
    alert("Remove DOM2 Event Handler");
}, false);

但这种传入匿名函数的事件处理程序无法移除。removeEventListener()必须和addEventListener()传入的事件处理函数必须是同一个。

var btn = document.getElementById('btn');
var handler = function() {
    alert("DOM2 Event Handler");
}
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false);    // 有效果

大多数情况下,事件处理程序会被添加到事件流的冒泡阶段,主要原因是跨浏览器兼容性好。把事件处理程序注册到捕获阶段通常用于在事件到达其指定目标之前拦截事件。如果不需要拦截,则不要使用事件捕获。

IE事件处理程序

IE 实现了 attachEvent()detachEvent() 方法用于事件处理程序的赋值和移除。接收两个同样的参数:事件处理程序的名字和事件处理函数。IE8以前只支持事件冒泡,使用attachEvent()会添加到冒泡阶段。

var btn = document.getElementById("btn");
btn.attachEvent("onclick", function() {
    alert("IE Event Handler");
});

此时的事件处理程序是在全局作用域中运行。this等于window。也可以给一个元素添加多个事件处理程序,但事件处理程序会以添加的顺序的反向触发。

事件对象

DOM发生事件时会将相关信息收集并存储在event对象中。该对象会包含一些事件的元素、类型等基本信息。

在浏览器中,event对象是传给事件处理程序的唯一参数。在添加事件处理程序时可以使用事件对象:


下表列出全部属性和方法:

属性/方法 类型 读/写 说明
bubbles 布尔值 只读 表示事件是否冒泡
cancelable 布尔值 只读 表示事件是否可以被取消
currentTarget 元素 只读 当前事件处理程序所在的元素
defaultPrevented 布尔值 只读 true表示已经调用preventDefault()方法(DOM3 Events中新增)
detail 整数 只读 只有UIEvent(用户界面)事件才具有。返回一个数值,表示事件的某种信息。如click事件,detail返回的是鼠标按下的次数。
eventPhase 整数 只读 表示事件流正被处理到了哪个阶段:1代表捕获阶段,2代表到达目标,3代表冒泡阶段
target 元素 只读 事件目标
isTrusted 布尔值 只读 true表示事件是由浏览器发起的。false表示事件是由脚本创建、修改、通过EventTarget.dispatchEvent()派发
type 字符串 只读 被触发的事件类型
preventDefault() 函数 只读 用于取消事件的默认行为。只有cancelable为true才可以调用这个方法
stopImmediatePropagation() 函数 只读 用于取消后续事件捕获或事件冒泡,并阻止调用任何后续事件处理程序(DOM3 Events中新增)
stopPropagation() 函数 只读 阻止捕获和冒泡阶段中当前事件的进一步传播

事件处理程序内部,对于currentTargettarget两个属性,将事件处理程序直接添加在目标上时,this和它们是相等的。


    

如果将事件处理程序添加到按钮的父结点上,结果就是currentTargetdocument.bodythis相等,因为它是注册事件处理程序的元素。而target属性等于按钮本身,因为click事件才是真正的目标。但按钮本身没注册事件处理程序,click事件冒泡到document.body,触发注册的处理程序。

document.body.onclick = function (event) {
    alert(event.currentTarget === document.body);    // true
    alert(this === document.body);    // true
    alert(event.target === this);    // false
    alert(event.target === document.getElementById("btn"));    // true
    }

preventDefault()方法用于阻止特定事件的默认动作。比如,链接的默认行为就是在被单机时导航到href属性指定的URL。如果想阻止这个导航行为,可以在onclick事件处理程序中取消,如下面的例子所示:

let link = document.getElementById('link');
link.onclick = function (event) {
    event.preventDefault();
}

任何可以通过preventDefault()取消默认行为的事件,其事件对象的cancelable属性都会设置为true

stopPropagation()方法用于立即阻止事件流在DOM结构中传播,取消后续的事件捕获或冒泡。例如,直接添加到按钮的事件处理程序中调用stopPropagation(),可以阻止document.body上注册事件处理程序执行。比如:

let btn = document.getElementById("btn");
btn.onclick = function(event) {
    console.log("Clicked");
    event.stopPropagation();
};
document.body.onclick = function(event) {
    console.log("Body clicked");
};

IE事件对象也包含与导致其创建的特定事件相关的属性和方法,很多都与DOM属性和方法对应。如srcElement对应DOM中的targetreturnValue属性对应DOM中的preventDefault()方法等等。

事件类型

DOM3 EventsDOM2 Events 基础上重新定义了并增加了新的事件类型:

  • UI Event:用户界面事件,涉及与 BOM 交互的通用浏览器事件。
  • Focus Event:焦点事件,在元素获得和失去焦点时触发。
  • Mouse Event:鼠标事件,使用鼠标在页面上执行某些操作时触发。
  • Wheel Event:滚轮事件,使用鼠标滚轮或类似设备时触发。
  • Input Event:输入事件,向文档中输入文本时触发。
  • Keyboard Event:键盘事件,使用键盘在页面上执行某些操作时触发。
  • Composition Event:合成事件,在使用某种 IME(Input Method Editor,输入法编辑器)输入字符时触发。

用户界面事件

用户界面事件或 UI 事件不一定跟用户操作有关。这类事件在 DOM 规范出现之前就已经以某种形式存在了,保留它们是为了向后兼容。UI 事件主要有以下几种。

  • load:在 window 上当页面加载完成后触发,在上当所有都加载完成后触发,在元素上当图片加载完成后触发,在元素上当相应对象加载完成后触发。
  • unload:在 window 上当页面完全卸载后触发,在上当所有都卸载完成后触发,在元素上当相应对象卸载完成后触发。
  • abort:在元素上当相应对象加载完成前被用户提前终止下载时触发。
  • error:在 window 上当 JavaScript 报错时触发,在 元素上当无法加载指定图片时触发,在 元素上当无法加载相应对象时触发,在上当一个或多个无法完成加载时触发。
  • select:在文本框(