【红宝书笔记精简版】第十七章 事件

目录

17.1 事件流

17.1.1 事件冒泡

17.1.2 事件捕获

17.2 事件处理程序

17.2.1 HTML 事件处理程序

17.2.2 DOM0 事件处理程序

17.2.3 DOM2 事件处理程序

17.2.4 IE 事件处理程序

17.2.5 跨浏览器事件处理程序

17.3 事件对象

17.3.1 DOM 事件对象

17.3.2 IE 事件对象

17.3.3 跨浏览器事件对象

17.4 事件类型

17.4.1 用户界面事件

17.4.2 焦点事件

17.4.3 鼠标和滚轮事件

17.4.4 键盘与输入事件

17.4.5 合成事件

17.4.6 变化事件

17.4.7 HTML5 事件

17.4.8 设备事件

17.4.9 触摸及手势事件

17.4.10 事件参考

17.5 内存与性能

17.5.1 事件委托

17.5.2 删除事件处理程序

17.6 模拟事件

17.6.1 DOM 事件模拟

17.6.2 IE 事件模拟

17.7 小结


17.1 事件流

当你点击一个按钮时,实际上不光点击了这个按钮,还点击了它的容器以及整 个页面。事件流描述了页面接收事件的顺序。结果非常有意思,IE 和 Netscape 开发团队提出了几乎完全相 反的事件流方案。IE 将支持事件冒泡流,而 Netscape Communicator 将支持事件捕获流。

17.1.1 事件冒泡

IE 事件流被称为事件冒泡,这是因为事件被定义为从最具体的元素(文档树中最深的节点)开始触 发,然后向上传播至没有那么具体的元素(文档)。

<!DOCTYPE html>
<html>
<head>
 <title>Event Bubbling Example</title>
</head>
<body>
 <div id="myDiv">Click Me</div>
</body>
</html>

在点击页面中的元素后,click 事件会以如下顺序发生:

<div>元素,即被点击的元素,最先触发 click 事件。然后,click 事件沿 DOM 树一 路向上,在经过的每个节点上依次触发,直至到达 document 对象。

17.1.2 事件捕获

事件捕获的意思是最不具体的节 点应该最先收到事件,而最具体的节点应该最后收到事件。事件捕获实际上是为了在事件到达最终目标 前拦截事件。在事件捕获中,click 事件首先由 document 元素捕获,然后沿 DOM 树依次向下传播,直至到达 实际的目标元素<div>

 17.1.3 DOM 事件流

DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。事件捕获最先发生, 为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个 阶段响应事件。

17.2 事件处理程序

事件意味着用户或浏览器执行的某种动作。比如,单击(click)、加载(load)、鼠标悬停 (mouseover)。为响应事件而调用的函数被称为事件处理程序(或事件监听器)。

17.2.1 HTML 事件处理程序

特定元素支持的每个事件都可以使用事件处理程序的名字以 HTML 属性的形式来指定。此时属性 的值必须是能够执行的 JavaScript 代码。例如,要在按钮被点击时执行某些 JavaScript 代码,可以使用以 下 HTML 属性:

<input type="button" value="Click Me" onclick="console.log('Clicked')"/>

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

<script>
 function showMessage() {
 console.log("Hello world!");
 }
</script>
<input type="button" value="Click Me" onclick="showMessage()"/>

17.2.2 DOM0 事件处理程序

JavaScript 中指定事件处理程序的传统方式是把一个函数赋值给(DOM 元素的)一个事件处理程 序属性。这也是在第四代 Web 浏览器中开始支持的事件处理程序赋值方法,直到现在所有现代浏览器 仍然都支持此方法,主要原因是简单。要使用 JavaScript 指定事件处理程序,必须先取得要操作对象的 引用。

let btn = document.getElementById("myBtn");
btn.onclick = function() {
 console.log("Clicked");
};

通过将事件处理程序属性的值设置为 null,可以移除通过 DOM0 方式添加的事件处理程序,如下 面的例子所示: 

btn.onclick = null; // 移除事件处理程序

把事件处理程序设置为 null,再点击按钮就不会执行任何操作了。 

17.2.3 DOM2 事件处理程序

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

let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
 console.log(this.id);
}, false); 

使用 DOM2 方式的主要优势是可以为同一个事件添加多个事件处理程序。来看下面的例子: 

let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
 console.log(this.id);
}, false);
btn.addEventListener("click", () => {
 console.log("Hello world!");
}, false); 

通过 addEventListener()添加的事件处理程序只能使用 removeEventListener()并传入与添 加时同样的参数来移除。 这意味着使用 addEventListener()添加的匿名函数无法移除,如下面的例 子所示:

let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
 console.log(this.id);
 }, false);
// 其他代码
btn.removeEventListener("click", function() { // 没有效果!
 console.log(this.id);
}, false); 
let btn = document.getElementById("myBtn");
let handler = function() {
 console.log(this.id);
};
btn.addEventListener("click", handler, false);
// 其他代码
btn.removeEventListener("click", handler, false); // 有效果!

这个例子有效,因为调用 addEventListener()和 removeEventListener()时传入的是同一个 函数。 

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

17.2.4 IE 事件处理程序

IE 实现了与 DOM 类似的方法,即 attachEvent()和 detachEvent()。这两个方法接收两个同样 的参数:事件处理程序的名字和事件处理函数。因为 IE8 及更早版本只支持事件冒泡,所以使用 attachEvent()添加的事件处理程序会添加到冒泡阶段。

要使用 attachEvent()给按钮添加 click 事件处理程序,可以使用以下代码:

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
 console.log("Clicked");
});

注意,attachEvent()的第一个参数是"onclick",而不是 DOM 的 addEventListener()方法 的"click"。 

 与使用 addEventListener()一样,使用 attachEvent()方法也可以给一个元素添加多个事件处 理程序。不过,与 DOM 方法不同,这里的事件处理程序会以添加它们的顺序反向触发。

使用 attachEvent()添加的事件处理程序将使用 detachEvent()来移除,只要提供相同的参数。 与使用 DOM 方法类似,作为事件处理程序添加的匿名函数也无法移除。

17.2.5 跨浏览器事件处理程序

为了以跨浏览器兼容的方式处理事件,很多开发者会选择使用一个 JavaScript 库,其中抽象了不同 浏览器的差异。

var EventUtil = {
 addHandler: function(element, type, handler) {
 if (element.addEventListener) {
 element.addEventListener(type, handler, false);
 } else if (element.attachEvent) {
 element.attachEvent("on" + type, handler);
 } else {
 element["on" + type] = handler;
 }
 },
 removeHandler: function(element, type, handler) {
 if (element.removeEventListener) {
 element.removeEventListener(type, handler, false);
 } else if (element.detachEvent) {
 element.detachEvent("on" + type, handler);
 } else {
 element["on" + type] = null;
 }
 }
};

17.3 事件对象

17.3.1 DOM 事件对象

在 DOM 合规的浏览器中,event 对象是传给事件处理程序的唯一参数。不管以哪种方式(DOM0 或 DOM2)指定事件处理程序,都会传入这个 event 对象。下面的例子展示了在两种方式下都可以使 用事件对象:

let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
 console.log(event.type); // "click"
};
btn.addEventListener("click", (event) => {
 console.log(event.type); // "click"
}, false);

 使用 event.type 属性确定了事件类型,从而可以做出不同的响应。 

let btn = document.getElementById("myBtn");
let handler = function(event) {
 switch(event.type) {
 case "click":
 console.log("Clicked");
 break;
 case "mouseover":
event.target.style.backgroundColor = "red";
 break;
 case "mouseout":
 event.target.style.backgroundColor = "";
 break;
 }
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;

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

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

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

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

 如果这个例子中不调用stopPropagation(),那么点击按钮就会打印两条消息。但这里由于click 事件不会传播到 document.body,因此 onclick 事件处理程序永远不会执行。

17.3.2 IE 事件对象

与 DOM 事件对象不同, IE 事件对象可以基于事件处理程序被指定的方式以不同方式来访问。如果 事件处理程序是使用 DOM0 方式指定的,则 event 对象只是 window 对象的一个属性:

var btn = document.getElementById("myBtn");
btn.onclick = function() {
 let event = window.event;
 console.log(event.type); // "click"
};

这里,window.event 中保存着 event 对象,其 event.type 属性保存着事件类型(IE 的这个属 性的值与 DOM 事件对象中一样)。不过,如果事件处理程序是使用 attachEvent()指定的,则 event 对象会作为唯一的参数传给处理函数,如下所示:

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(event) {
 console.log(event.type); // "click"
});

17.3.3 跨浏览器事件对象

17.4 事件类型

DOM3 Events 定义了如下事件类型。
用户界面事件(UIEvent):涉及与 BOM 交互的通用浏览器事件。
焦点事件(FocusEvent):在元素获得和失去焦点时触发。
鼠标事件(MouseEvent):使用鼠标在页面上执行某些操作时触发。
滚轮事件(WheelEvent):使用鼠标滚轮(或类似设备)时触发。
输入事件(InputEvent):向文档中输入文本时触发。
键盘事件(KeyboardEvent):使用键盘在页面上执行某些操作时触发。
合成事件(CompositionEvent):在使用某种 IME(Input Method Editor,输入法编辑器)输入 字符时触发。

17.4.1 用户界面事件

UI 事件主要有以下几种。
  load:在 window 上当页面加载完成后触发,在窗套()上当所有窗格() 都加载完成后触发,在元素上当图片加载完成后触发,在元素上当相应对象加 载完成后触发。
 unload:在 window 上当页面完全卸载后触发,在窗套上当所有窗格都卸载完成后触发,在元素上当相应对象卸载完成后触发。
 abort:在元素上当相应对象加载完成前被用户提前终止下载时触发。
 error:在 window 上当 JavaScript 报错时触发,在元素上当无法加载指定图片时触发, 在元素上当无法加载相应对象时触发,在窗套上当一个或多个窗格无法完成加载时 触发。
 select:在文本框(或 textarea)上当用户选择了一个或多个字符时触发。
 resize:在 window 或窗格上当窗口或窗格被缩放时触发。
 scroll:当用户滚动包含滚动条的元素时在元素上触发。元素包含已加载页面的滚动条。 大多数 HTML 事件与 window 对象和表单控件有关。

17.4.2 焦点事件

当焦点从页面中的一个元素移动到另一个元素,会依次触发下列事件: (1) focusout 在失去焦点的元素上触发; (2) focusin 在获得焦点的元素上触发; (3) blur 在失去焦点的元素上触发; (4) DOMFocusOut 在失去焦点的元素上触发; (5) focus 在获得焦点的元素上触发; (6) DOMFocusIn 在获得焦点的元素上触发。

17.4.3 鼠标和滚轮事件

 click:在用户单击主鼠标按钮(一般是左边的按钮)或者按下回车键时触发。这一点对确保 易访问性很重要,意味着 onclick 事件处理程序既可以通过键盘也可以通过鼠标执行。
 dblclick:在用户双击主鼠标按钮(一般是左边的按钮)时触发。
 mousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。
 mouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且 在光标移动到后代元素上不会触发。
 mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且 在光标移动到后代元素上不会触发。
 mousemove:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件

只有在同一个元素上相继触发 mousedown 和 mouseup 事件,才会触发 click 事件;如果 mousedown 或 mouseup 中的一个被取消,就不会触发 click 事件。类似地,只有触发两次 click 事 件,才会触发一次 dblclick 事件。如果有代码阻止了连续两次触发 click 事件(可能是直接取消 click 事件,也可能通过取消 mousedown 或 mouseup 间接实现),那么就不会触发 dblclick 事件了。

1. 客户区坐标位置

鼠标事件都是在浏览器视口中的特定位置上发生的。这个位置信息保存在事件对象的 clientX 和 clientY 属性中。所有浏览器都支持这两个属性,它们的值表示事件发生时鼠标指针在视口中的水平 和垂直坐标。

 可以使用类似下列代码取得鼠标事件的客户端坐标信息:

var div = document.getElementById("myDiv"); 
EventUtil.addHandler(div, "click", function(event){ 
 event = EventUtil.getEvent(event); 
 alert("Client coordinates: " + event.clientX + "," + event.clientY); 
}); 

注意,这些值中不包括页面滚动的距离,因此这个位置并不表示鼠标在页面上的位置。 

通过客户区坐标能够知道鼠标是在视口中什么位置发生的,而页面坐标通过事件对象的 pageX 和 pageY 属性,能告诉你事件是在页面中的什么位置发生的。换句话说,这两个属性表示鼠标光标在页面 中的位置,因此坐标是从页面本身而非视口的左边和顶边计算的。 

var div = document.getElementById("myDiv"); 
EventUtil.addHandler(div, "click", function(event){ 
 event = EventUtil.getEvent(event); 
 alert("Page coordinates: " + event.pageX + "," + event.pageY); 
}); 

在页面没有滚动的情况下,pageX 和 pageY 的值与 clientX 和 clientY 的值相等。 

IE8 及更早版本不支持事件对象上的页面坐标,不过使用客户区坐标和滚动信息可以计算出来。这 时候需要用到 document.body(混杂模式)或 document.documentElement(标准模式)中的 scrollLeft 和 scrollTop 属性。计算过程如下所示:

var div = document.getElementById("myDiv"); 
EventUtil.addHandler(div, "click", function(event){ 
 event = EventUtil.getEvent(event); 
 var pageX = event.pageX, 
 pageY = event.pageY; 
 if (pageX === undefined){ 
 pageX = event.clientX + (document.body.scrollLeft || 
 document.documentElement.scrollLeft); 
 } 
 if (pageY === undefined){ 
 pageY = event.clientY + (document.body.scrollTop || 
 document.documentElement.scrollTop); 
 } 
 alert("Page coordinates: " + pageX + "," + pageY); 
}); 

3. 屏幕坐标位置 

鼠标事件发生时,不仅会有相对于浏览器窗口的位置,还有一个相对于整个电脑屏幕的位置。而通 过 screenX 和 screenY 属性就可以确定鼠标事件发生时鼠标指针相对于整个屏幕的坐标信息。

var div = document.getElementById("myDiv"); 
EventUtil.addHandler(div, "click", function(event){ 
 event = EventUtil.getEvent(event); 
 alert("Screen coordinates: " + event.screenX + "," + event.screenY); 
});

17.4.4 键盘与输入事件

有 3 个键盘事件,简述如下。
 keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。
 keypress:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。 按下 Esc 键也会触发这个事件。Safari 3.1 之前的版本也会在用户按下非字符键时触发 keypress 事件。
 keyup:当用户释放键盘上的键时触发

17.4.5 合成事件

合成事件有以下 3 种:
 compositionstart,在 IME 的文本合成系统打开时触发,表示输入即将开始;
 compositionupdate,在新字符插入输入字段时触发;
 compositionend,在 IME 的文本合成系统关闭时触发,表示恢复正常键盘输入。

17.4.6 变化事件

DOM2 的变化事件(Mutation Events)是为了在 DOM 发生变化时提供通知。

注意 这些事件已经被废弃,浏览器已经在有计划地停止对它们的支持。变化事件已经被 Mutation Observers 所取代,可以参考第 14 章中的介绍。【红宝书笔记精简版】第十四章DOM_小柒很爱喵的博客-CSDN博客

17.4.7 HTML5 事件

17.4.8 设备事件

1. orientationchange 事件

苹果公司在移动 Safari 浏览器上创造了 orientationchange 事件,以方便开发者判断用户的设备 是处于垂直模式还是水平模式。移动 Safari 在 window 上暴露了 window.orientation 属性,它有以 下 3 种值之一:0 表示垂直模式,90 表示左转水平模式(主屏幕键在右侧),–90 表示右转水平模式(主 屏幕键在左)。虽然相关文档也提及设备倒置后的值为 180,但设备本身至今还不支持。

2. deviceorientation 事件

如果可以获取设备的加速计信息, 而且数据发生了变化,这个事件就会在 window 上触发。要注意的是,deviceorientation 事件只反 映设备在空间中的朝向,而不涉及移动相关的信息。

3. devicemotion 事件

DeviceOrientationEvent 规范也定义了 devicemotion 事件。这个事件用于提示设备实际上在移动, 而不仅仅是改变了朝向。例如,devicemotion 事件可以用来确定设备正在掉落或者正拿在一个行走的 人手里。

17.4.9 触摸及手势事件

1. 触摸事件

 touchstart:当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发。
 touchmove:当手指在屏幕上滑动时连续地触发。在这个事件发生期间,调用preventDefault() 可以阻止滚动。
 touchend:当手指从屏幕上移开时触发。
 touchcancel:当系统停止跟踪触摸时触发。关于此事件的确切触发时间,文档中没有明确说明。 上面这几个事件都会冒泡,也都可以取消。虽然这些触摸事件没有在 DOM 规范中定义,但它们却 是以兼容 DOM 的方式实现的。因此,每个触摸事件的 event 对象都提供了在鼠标事件中常见的属性: bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、 ctrlKey 和 metaKey。

除了常见的 DOM 属性外,触摸事件还包含下列三个用于跟踪触摸的属性。
 touches:表示当前跟踪的触摸操作的 Touch 对象的数组。
 targetTouchs:特定于事件目标的 Touch 对象的数组。
 changeTouches:表示自上次触摸以来发生了什么改变的 Touch 对象的数组。

每个 Touch 对象包含下列属性。

 clientX:触摸目标在视口中的 x 坐标。
 clientY:触摸目标在视口中的 y 坐标。
 identifier:标识触摸的唯一 ID。
 pageX:触摸目标在页面中的 x 坐标。
 pageY:触摸目标在页面中的 y 坐标。
 screenX:触摸目标在屏幕中的 x 坐标。
 screenY:触摸目标在屏幕中的 y 坐标。
 target:触摸的 DOM 节点目标。

2. 手势事件

 gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发。
 gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发。
 gestureend:当任何一个手指从屏幕上面移开时触发。

17.4.10 事件参考

17.5 内存与性能

17.5.1 事件委托

事件委托利用了事件冒泡,只指定一个事 件处理程序,就可以管理某一类型的所有事件。例如,click 事件会一直冒泡到 document 层次。也就 是说,我们可以为整个页面指定一个 onclick 事件处理程序,而不必给每个可单击的元素分别添加事 件处理程序。

最适合采用事件委托技术的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress。 虽然 mouseover 和 mouseout 事件也冒泡,但要适当处理它们并不容易,而且经常需要计算元素的位置。 (因为当鼠标从一个元素移到其子节点时,或者当鼠标移出该元素时,都会触发 mouseout 事件。)

17.5.2 删除事件处理程序

把事件处理程序指定给元素后,在浏览器代码和负责页面交互的 JavaScript 代码之间就建立了联系。 这种联系建立得越多,页面性能就越差。除了通过事件委托来限制这种连接之外,还应该及时删除不用 的事件处理程序。很多 Web 应用性能不佳都是由于无用的事件处理程序长驻内存导致的。

17.6 模拟事件

17.6.1 DOM 事件模拟

1. 模拟鼠标事件

2. 模拟键盘事件

3. 模拟其他事件

4. 自定义 DOM 事件

17.6.2 IE 事件模拟

17.7 小结

事件是将 JavaScript 与网页联系在一起的主要方式。“DOM3 级事件”规范和 HTML5 定义了常见的 大多数事件。即使有规范定义了基本事件,但很多浏览器仍然在规范之外实现了自己的专有事件,从而 为开发人员提供更多掌握用户交互的手段。有些专有事件与特定设备关联,例如移动 Safari 中的 orientationchange 事件就是特定关联 iOS 设备的。 在使用事件时,需要考虑如下一些内存与性能方面的问题。
 有必要限制一个页面中事件处理程序的数量,数量太多会导致占用大量内存,而且也会让用户 感觉页面反应不够灵敏。
 建立在事件冒泡机制之上的事件委托技术,可以有效地减少事件处理程序的数量。
 建议在浏览器卸载页面之前移除页面中的所有事件处理程序。 可以使用 JavaScript 在浏览器中模拟事件。

“DOM2 级事件”和“DOM3 级事件”规范规定了模拟事 件的方法,为模拟各种有定义的事件提供了方便。此外,通过组合使用一些技术,还可以在某种程度上 模拟键盘事件。IE8 及之前版本同样支持事件模拟,只不过模拟的过程有些差异。 事件是 JavaScript 中最重要的主题之一,深入理解事件的工作机制以及它们对性能的影响至关重要。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值