第17章 事件
事件代表文档或浏览器窗口中某个有意义的时刻。可以使用仅在事件发生时执行的监听器(也叫处理程序)订阅事件。
# 事件流
点击按钮时,是否点击了<body>标签?是的,这里涉及到事件流的顺序。
# 事件冒泡
是事件从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)。对于一段html:
<!DOCTYPE html>
<html>
<head> </head>
<body>
<button>click</button>
</body>
</html>
2
3
4
5
6
7
点击button后,click事件顺序如下:
button>body>html>document。
这就是事件冒泡,所有现代浏览器都支持该方式。
# 事件捕获
事件捕获刚好相反,从document最开始接收到事件,直到最具体的元素。即上面的例子事件顺序为:
document>html>body>button。
所有现代浏览器都支持该方式。
# DOM事件流
DOM2 Events规范规定,事件流分为3个阶段:事件捕获、到达目标和事件冒泡。
注意,在事件处理时,被认为是冒泡的一部分,如果要阻止事件传播,则会在这里进行(个人理解)。
# 事件处理程序
是指为响应事件而调用的函数,比如单击(click)、加载(load)等。事件处理程序有3种写法。
// 第1种
<button onclick="console.log(event.type)">click</button>
2
// 第2种
<button onclick="say()">click</button>
<script>
function say() {
console.log(event.type)
}
</script>
2
3
4
5
6
7
// 第3种
<button>click</button>
<script>
const btn = document.querySelector('button')
btn.onclick = say
function say() {
console.log(event.type)
}
</script>
2
3
4
5
6
7
8
9
如果事件处理函数中有this,则this指向元素本身。
将事件处理程序属性的值设置为null,就可以移除事件处理程序。
btn.onclick = null
除了使用onclick属性的方式,还可以使用addEventListener()
和removeEventListener()
方法来监听和解除监听。
btn.addEventListener('click', function () {
console.log(this.id)
})
2
3
注意,上面这种方式添加的匿名函数无法移除,必须是同一个事件处理函数。
# 事件对象
DOM事件发生时,所有相关信息都会收集到一个event对象中。
如何区分event.target和event.currentTarget?
- target,事件的实际目标。
- currentTarget,当前事件处理程序所在的元素,即this指向的元素。
上面按钮的target和currentTarget是同一个元素,如果改成监听document.body
,则情况有所不同。
document.body.onclick = function (event) {
console.log(event.currentTarget) // body
console.log(event.target) // button
}
2
3
4
preventDefault(),用于阻止特定事件的默认行为。
stopPropagation(),用于立即阻止事件流在DOM结构中传播,取消后续的事件捕获或冒泡。
event对象只在事件处理程序执行期间存在,一旦执行完毕,就会被销毁。
# 事件类型
DOM3 Events定义了如下事件类型。
用户界面事件
load:有2类,一类是window上页面加载完成后触发;一类是img元素上图片加载完成后触发。
// 窗口 window.addEventListener('load', (event) => { console.log('loaded') }) // 图片 let img = document.createElement('img') img.addEventListener('load', (event) => { console.log(event.target.src) }) document.body.appendChild(img) img.src = 'example.gif'
1
2
3
4
5
6
7
8
9
10
11注意,加载图片不一定要把img元素添加到文档,只要给它设置了src属性就会立即开始下载。
除了使用DOM元素,也可以直接使用Image对象。
let img = new Image()
1unload: 在window上当页面完全卸载后触发。一般是从一个页面导航到另一个页面是触发,常用于清理引用。
resize:当浏览器窗口被缩放到新高度或宽度时,会触发resize事件。
scroll:页面滚动事件。
焦点事件
- blur:当元素失去焦点时触发,不冒泡。
- focus:当元素获得焦点时触发,不冒泡。
- focusin:当元素获得焦点时触发,会冒泡。
- focusout:当元素失去焦点时触发,会冒泡。
鼠标和滚轮事件
- click:用户单击鼠标左键或键盘回车键时触发。
- dblclick:双击鼠标主键时触发。
- mousedown:按下任意鼠标键时触发。
- mouseenter:用户把鼠标光标从元素外部移到元素内部时触发。
- mouseleave:用户把鼠标光标从元素内部移到元素外部时触发。
- mousemove:鼠标光标在元素上移动时反复触发。
- mouseout:用户把鼠标光标从一个元素移动到另一个元素上时触发。
- mouseover:用户把鼠标光标从元素外部移到元素内部时触发。
- mouseup:用户释放鼠标键时触发。
- mousewheel:鼠标滚轮滚动时触发。
区分鼠标事件中event对象的属性,如下:
clientX和clientY:表示事件发生时鼠标光标在视口中的坐标,与页面滚动无关。
pageX和pageY:表示事件发生时鼠标光标在页面上的坐标,反映的是光标到页面而非视口左边和上边的距离。
screenX和screenY:表示光标在屏幕上的坐标,跟浏览器的位置和大小无关。
shiftKey、ctrlKey、altKey、metaKey:有时鼠标事件发生时,需要键盘按键辅助,提供了4个修饰键表示是否被按下。
button:表示鼠标按键类型:0表示主键,1表示中键,2表示副键。
键盘与输入事件
- keydown,用户按下键盘某个键时触发,可以重复触发。
- keypress,DOM3废弃了该事件,推荐textInput。
- keyup,用户释放键盘上某个键时触发。
- 键码,event.keyCode属性,表示键盘上特定的键。
- textInput,只在可编辑区域上且新字符被插入时触发。
HTML5事件
contextmenu:显示上下文菜单(在Windows上是右击鼠标,Mac上是Ctrl+单击)时触发,网站可拦截该事件从而自定义右键菜单。
<!DOCTYPE html> <html> <head> <style> * { margin: 0; padding: 0; } .bg { width: 100vw; height: 100vh; position: relative; } ul { visibility: hidden; width: 100px; position: absolute; } li { height: 30px; border: 1px solid; list-style: none; background-color: lightblue; } </style> </head> <body> <div class="bg"> <ul class="menu"> <li>自定义1</li> <li>自定义2</li> <li>自定义3</li> </ul> </div> </body> </html> <script> const bg = document.querySelector('.bg') const ul = document.querySelector('ul') const li = document.querySelector('li') bg.addEventListener('contextmenu', (event) => { event.preventDefault() console.log(event.clientX, event.clientY) ul.style.left = event.clientX + 'px' ul.style.top = event.clientY + 'px' ul.style.visibility = 'visible' }) document.addEventListener('click', () => { ul.style.visibility = 'hidden' }) </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53beforeuload:给开发者提供阻止页面被卸载的机会
window.addEventListener('beforeunload',(event)=>{ let message = '确认退出吗' event.returnValue = message return message })
1
2
3
4
5DOMContentLoaded:DOM树构建完成后立即触发,不需要等待外部资源加载完成,从而让用户能够更快地与页面交互。
hashchange:url
#
后的值发生变化时触发,常用于单页应用的路由导航。
设备事件
- orientationchange,判断用户设备处于垂直模式还是水平模式。
- deviceorientation:获取设备的加速计信息,可以用于摇一摇功能。
- devicemotion:检测设备的移动信息。
触摸和手势事件
- touchstart:手指放到屏幕上时触发。
- touchmove:手指在屏幕上滑动时连续触发。
- touchend:手指从屏幕上移开时触发。
- touchcancel:系统停止跟踪触摸时触发。
- gesturestart:一个手指已经放在屏幕上,另一个手指开始放到屏幕时触发。
- gesturechange:任意一个手指在屏幕上的位置发生变化时触发。
- gestureend:其中一个手指离开屏幕时触发。
# 内存与性能
页面中事件处理程序的数量与页面整体性能直接相关,原因有2点:
- 每个函数都是对象,数量越多,占用内存越大。
- 为事件处理程序绑定DOM后,会先去访问DOM,造成页面交互延迟。
从这2点出发,诞生出事件委托和删除事件处理程序2个办法。
# 事件委托
利用事件冒泡,可以使用一个祖先节点的事件来管理一种类型的事件,而不用每个节点都绑定事件。
# 删除事件处理程序
事件绑定到元素后,双方就建立联系,即使DOM树被移除,由于事件绑定关系未解除,DOM节点实际并没有真正释放,就不会被垃圾回收机制清理。比如:
<!DOCTYPE html>
<head>
</head>
<body>
<div class="myDiv">
<button>click me</button>
</div>
</body>
</html>
<script>
const btn = document.querySelector('button')
const myDiv = document.querySelector('.myDiv')
btn.onclick = function(){
btn.onclick = null // 必要!删除事件处理程序
myDiv.innerHTML = 'Loading'
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18