一、事件简介
事件冒泡
事件捕获
DOM事件
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
二、事件处理
HTML事件处理
通过 event 变量,可以直接访问事件对象,你不用自己定义它,也不用从函数的参数列表中读取。
<!-- 输出 "click" -->
<input type="button" value="Click Me" onclick="alert(event.type)">
可能的问题:
- 有可能 HTML 元素已经显示在页面上,用户都与其交互了,而事件处理程序的代码还无法执行。
- 对事件处理程序作用域链的扩展在不同浏览器中可能导致不同的结果。访问无限定的对象成员可能导致错误。
- HTML 与 JavaScript 强耦合。
DOM0 事件处理程序
let btn = document.getElementById("myBtn");
btn.onclick = function() {
console.log("Clicked");
};
关注点:
- 如果在页面中上面的代码出现在按钮之后,则有可能出现用户点击按钮没有反应的情况。
- 事件处理程序会在元素的作用域中运行,即 this 等于元素。
- 通过将事件处理程序属性的值设置为 null,可以移除通过 DOM0 方式添加的事件处理程序。
DOM2 事件处理程序
addEventListener()和 removeEventListener()。这两个方法暴露在所有 DOM 节点上,它们接收 3 个参数:事件名、事件处理函数和一个布尔值,true 表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序
- 其中的event,必须。字符串,指定事件名。 不要使用 “on” 前缀。 例如,使用 “click” ,而不是使用 “onclick”。
- function 必须。指定要事件触发时执行的函数
- useCapture 可选。布尔值,指定事件是否在捕获或冒泡阶段执行。默认false
事件的整体顺序是:非目标元素捕获 -> 目标元素代码顺序 -> 非目标元素冒泡。
注意:
- 多个事件处理程序以添加顺序来触发
- 通过 addEventListener()添加的事件处理程序只能使用 removeEventListener()并传入与添加时同样的参数来移除。这意味着使用 addEventListener()添加的匿名函数无法移除。
- 大多数情况下,事件处理程序会被添加到事件流的冒泡阶段,主要原因是跨浏览器兼容性好。把事件处理程序注册到捕获阶段通常用于在事件到达其指定目标之前拦截事件。
IE 事件处理程序
attachEvent
detachEvent
跨浏览器事件处理程序
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;
}
}
};
三、事件对象
DOM事件对象(event)
在事件处理程序内部,this 对象始终等于 currentTarget 的值,而 target 只包含事件的实际目标。
type 属性在一个处理程序处理多个事件时很有用。
preventDefault()方法用于阻止特定事件的默认动作。
stopPropagation()方法用于取消所有后续事件捕获或事件冒泡。只有 bubbles为 true 才可以调用这个方法
eventPhase 属性可用于确定事件流当前所处的阶段。当 eventPhase 等于 2 时,this、target 和 currentTarget 三者相等。
var EventUtil = {
addHandler: function(element, type, handler) {
// 为节省版面,删除了之前的代码
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function(element, type, handler) {
// 为节省版面,删除了之前的代码
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
四、事件类型
DOM3 Events 定义了如下事件类型。
用户界面事件(UIEvent):涉及与 BOM 交互的通用浏览器事件。
焦点事件(FocusEvent):在元素获得和失去焦点时触发。
鼠标事件(MouseEvent):使用鼠标在页面上执行某些操作时触发。
滚轮事件(WheelEvent):使用鼠标滚轮(或类似设备)时触发。
输入事件(InputEvent):向文档中输入文本时触发。
键盘事件(KeyboardEvent):使用键盘在页面上执行某些操作时触发。
合成事件(CompositionEvent):在使用某种 IME(Input Method Editor,输入法编辑器)输入
字符时触发。
除了这些事件类型之外,HTML5 还定义了另一组事件,而浏览器通常在 DOM 和 BOM 上实现专有事
件。这些专有事件基本上都是根据开发者需求而不是按照规范增加的,因此不同浏览器的实现可能不同。
4.1 用户界面事件
load 事件
可以通过两种方式指定 load 事件处理程序。
第一种是 JavaScript 方式:
window.addEventListener("load", (event) => {
console.log("Loaded!");
});
第二种指定 load 事件处理程序的方式是向元素添加 onload 属性:(保证向后兼容的一个策略)
<body onload="console.log('Loaded!')">
</body>
unload 事件
load 事件相对的是 unload 事件,unload 事件会在文档卸载完成后触发。unload 事件一般是在从一个页面导航到另一个页面时触发,最常用于清理引用,以避免内存泄漏。
因为 unload 事件是在页面卸载完成后触发的,所以不能使用页面加载后才有的对象。此时要访问 DOM 或修改页面外观都会导致错误。
resize 事件
当浏览器窗口被缩放到新高度或宽度时,会触发 resize 事件。这个事件在 window 上触发
scroll 事件
4.2 焦点事件
焦点事件有以下 6 种。
blur:当元素失去焦点时触发。这个事件不冒泡,所有浏览器都支持。
DOMFocusIn:当元素获得焦点时触发。这个事件是 focus 的冒泡版。Opera 是唯一支持这个事
件的主流浏览器。DOM3 Events 废弃了 DOMFocusIn,推荐 focusin。 DOMFocusOut:当元素失去焦点时触发。这个事件是 blur 的通用版。Opera 是唯一支持这个事
件的主流浏览器。DOM3 Events 废弃了 DOMFocusOut,推荐 focusout。 focus:当元素获得焦点时触发。这个事件不冒泡,所有浏览器都支持。
focusin:当元素获得焦点时触发。这个事件是 focus 的冒泡版。
focusout:当元素失去焦点时触发。这个事件是 blur 的通用版。
4.3 鼠标和滚轮事件
1.鼠标事件
DOM3 Events定义了 9 种鼠标事件。
click:在用户单击鼠标主键(通常是左键)或按键盘回车键时触发。
dblclick:在用户双击鼠标主键(通常是左键)时触发。(触屏设备不支持双击)
mousedown:在用户按下任意鼠标键时触发。这个事件不能通过键盘触发
mouseup:在用户释放鼠标键时触发。这个事件不能通过键盘触发。
mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发
mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发
mousemove:在鼠标光标在元素上移动时反复触发。这个事件不能通过键盘触发。
mouseout:在用户把鼠标光标从一个元素移到另一个元素上时触发。移到的元素可以是原始元素的外部元素,也可以是原始元素的子元素。这个事件不能通过键盘触发。
mouseover:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不能通过键盘触发。
mouseout、mouseover和mouseleave、mouseenter区别
2.滚轮事件
鼠标事件在 DOM3 Events 中对应的类型是"MouseEvent"
鼠标事件还有一个名为滚轮事件的子类别。滚轮事件只有一个事件 mousewheel
3.坐标
客户端坐标
clientX 事件属性返回当事件被触发时鼠标指针向对于浏览器页面(或客户区)的水平坐标。
页面坐标
pageX 是一个由MouseEvent接口返回的相对于整个文档的x(水平)坐标以像素为单位的只读属性。
在页面没有滚动时,pageX 和 pageY 与 clientX 和 clientY 的值相同
屏幕坐标
screenX 和 screenY 属性获取鼠标光标在屏幕上的坐标。
4.特殊按键
键盘上的修饰键 Shift、Ctrl、Alt 和 Meta 经常用于修改鼠标事件的行为。DOM 规定了 4 个属性来表示这几个修饰键的状态:shiftKey、ctrlKey、altKey 和 metaKey(MAC)。这几个属性会在各自对应的修饰键被按下时包含布尔值 true,没有被按下时包含 false。
5. 相关元素
relatedTarget 属性(这个属性只有在 mouseover和 mouseout 事件发生时才包含值,其他所有事件的这个属性的值都是 null。)
在 mouseover 事件触发时,IE会提供 fromElement 属性,其中包含相关元素。而在 mouseout 事件触发时,IE 会提供 toElement属性,其中包含相关元素。(IE8 及更早版本)
6. 鼠标按键
对 mousedown 和 mouseup 事件来说,event 对象上会有一个 button 属性,表示按下或
释放的是哪个按键。DOM 为这个 button 属性定义了 3 个值:0 表示鼠标主键、1 表示鼠标中键(通常也是滚轮键)、2 表示鼠标副键。
<body>
<button id="mybtn">btn</button>
</body>
<script>
let mybtn = document.getElementById('mybtn')
mybtn.addEventListener('mouseup',(event)=>{
console.log(event.button)
})
</script>
7. 额外事件信息
DOM2 Events 规范在 event 对象上提供了 detail 属性,以给出关于事件的更多信息。
- detail 包含一个数值,表示在给定位置上发生了多少次单击。
- 单击相当于在同一个像素上发生一次 mousedown 紧跟一次 mouseup
- 如果鼠标在 mousedown和 mouseup 之间移动了,则 detail 会重置为 0。
8.mousewheel 鼠标滚轮事件
通过event.wheelDelta的正负号,可以获取滚轮上下滑动情况
4.4 键盘与输入事件
- 对于 keydown 和 keyup 事件,event 对象的 keyCode 属性中会保存一个键码
- 对于字母和数字键,keyCode 的值与小写字母和数字的 ASCII 编码一致。
4.5 HTML事件
DOMContentLoaded 事件:window 的 load 事件会在页面完全加载后触发,因为要等待很多外部资源加载完成,所以会花费较长时间。而 DOMContentLoaded 事件会在 DOM 树构建完成后立即触发,而不用等待图片、JavaScript文件、CSS 文件或其他资源加载完成。
hashchange 事件:onhashchange 事件处理程序必须添加给 window,每次 URL 散列值发生变化时会调用它。event对象有两个新属性:oldURL 和 newURL。这两个属性分别保存变化前后的 URL,而且是包含散列值的完整 URL。
4.6 设备事件
orientationchange 事件
window.orientation 属性: 0 表示垂直模式,90 表示左转水平模式(主屏幕键在右侧),–90 表示右转水平模式(主屏幕键在左)。
所有 iOS 设备都支持 orientationchange 事件和 window.orientation 属性。
当 deviceorientation 触发时,event 对象中会包含各个轴相对于设备静置时坐标值的变化,主要是以下 5 个属性。
alpha:0~360 范围内的浮点值,表示围绕 z 轴旋转时 y 轴的度数(左右转)。
beta:–180~180 范围内的浮点值,表示围绕 x 轴旋转时 z 轴的度数(前后转)。
gamma:–90~90 范围内的浮点值,表示围绕 y 轴旋转时 z 轴的度数(扭转)。
absolute:布尔值,表示设备是否返回绝对值。
compassCalibrated:布尔值,表示设备的指南针是否正确校准
devicemotion 事件:这个事件用于提示设备实际上在移动,而不仅仅是改变了朝向。
4.7 触摸及手势事件
触摸事件
触摸事件有如下几种。
touchstart:手指放到屏幕上时触发(即使有一个手指已经放在了屏幕上)。
touchmove:手指在屏幕上滑动时连续触发。在这个事件中调用 preventDefault()可以阻止滚动。
touchend:手指从屏幕上移开时触发。
touchcancel:系统停止跟踪触摸时触发。
这些事件都会冒泡,也都可以被取消。尽管触摸事件不属于 DOM 规范,但浏览器仍然以兼容 DOM的方式实现了它们。因此,每个触摸事件的 event 对象都提供了鼠标事件的公共属性:bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey 和 metaKey。
除了这些公共的 DOM 属性,触摸事件还提供了以下 3 个属性用于跟踪触点。
touches:Touch 对象的数组,表示当前屏幕上的每个触点。
targetTouches:Touch 对象的数组,表示特定于事件目标的触点。
changedTouches:Touch 对象的数组,表示自上次用户动作之后变化的触点。
每个 Touch 对象都包含下列属性。
clientX:触点在视口中的 x 坐标。
clientY:触点在视口中的 y 坐标。
identifier:触点 ID。 pageX:触点在页面上的 x 坐标。
pageY:触点在页面上的 y 坐标。
screenX:触点在屏幕上的 x 坐标。
screenY:触点在屏幕上的 y 坐标。
target:触摸事件的事件目标。
手势事件
手势事件有以下 3 种。
gesturestart:一个手指已经放在屏幕上,再把另一个手指放到屏幕上时触发。
gesturechange:任何一个手指在屏幕上的位置发生变化时触发。
gestureend:其中一个手指离开屏幕时触发。
只有在两个手指同时接触事件接收者时,这些事件才会触发。
当一个手指放在屏幕上时,会触发 touchstart 事件。当另一个手指放到屏幕上时,gesturestart 事件会首先触发,然后紧接着触发这个手指的 touchstart事件。如果两个手指或其中一个手指移动,则会触发 gesturechange 事件。只要其中一个手指离开屏幕,就会触发 gestureend 事件,紧接着触发该手指的 touchend 事件。
每个手势事件的 event 对象都包含所有标准的鼠标事件属性:bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey 和 metaKey。新增的个 event 对象属性是 rotation 和 scale。rotation 属性表示手指变化旋转的度数,负值表示逆时针旋转,正值表示顺时针旋转(从 0 开始)。scale 属性表示两指之间距离变化(对捏)的程度。开始时为 1,然后随着距离增大或缩小相应地增大或缩小
五、事件委托/删除事件
事件委托
场景:过多事件的处理
原理:事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件
最适合使用事件委托的事件包括:click、mousedown、mouseup、keydown 和 keypress。
事件删除
很多 Web 应用性能不佳都是由于无用的事件处理程序长驻内存导致的。
原因:
- 第一个是删除带有事件处理程序的元素。(removeChild()或 replaceChild(),元素删除了,内存中的事件没有删除)
- innerHTML 整体替换页面的某一部分(同上元素被替换,但是内存中的事件没有删除)
如果知道某个元素会被删除,那么最好在删除它之前手工删除它的事件处理程序
<script type="text/javascript">
let btn = document.getElementById("myBtn");
btn.onclick = function() {
// 执行操作
btn.onclick = null; // 删除事件处理程序
document.getElementById("myDiv").innerHTML = "Processing...";
};
</script>
页面卸载:如果在页面卸载后事件处理程序没有被清理,则它们仍然会残留在内存中
解决:一般来说,最好在 onunload 事件处理程序中趁页面尚未卸载先删除所有事件处理程序。这时候也能体现使用事件委托的优势,因为事件处理程序很少,所以很容易记住要删除哪些。关于卸载页面时的清理,可以记住一点:onload 事件处理程序中做了什么,最好在 onunload 事件处理程序中恢复。(在页面中使用 onunload 事件处理程序意味着页面不会被保存在往返缓存)