事件处理
事件处理定义方式
addEventListener
简介那一章我们就学过,使用 DOM 对象的 addEventListener
方法 ,可以告知浏览器,当某个事件发生时,执行哪个函数进行处理
比如
// 鼠标点击事件
element.addEventListener("click", handleFunc )
// 键盘事件
element.addEventListener("keydown", handleFunc )
前面的教程给了一个 处理 鼠标点击
事件 click
的例子,
我们还可以改为 处理 键盘按键按下
事件 keydown
。
文本输入框中,键盘按键 Ctrl + 回车
,就会执行script里面的函数,把薪资在 2万 以上、以下的人员名单分别打印出来
如下
<p>请输入员工薪资记录</p>
<textarea id="salary" rows="10" cols="50">
薛蟠 45600 25
薛宝钗 25776 23
</textarea>
<div>
<br><br>
<span id="result" style='color:blue'>
分类结果</span> <br><br>
<pre>
</pre>
<div>
<button onclick='location.reload()'>重置</button>
<script>
function salaryStats(event){
// 如果ctrl键按下,并且按下了回车键
if (event.ctrlKey && event.key==='Enter') {
document.querySelector('pre').innerText = `处理结果省略...`
}
}
// 注册事件回调函数
document.querySelector('#salary').addEventListener("keydown", salaryStats );
</script>
DOM对象事件属性
除了使用 addEventListener
指定 事件处理函数,
还可以使用 元素对应 DOM 对象的事件属性 指定 事件处理函数
事件属性名是 on
开头,后面加事件名称
比如: onkeydown
、 onclick
等等
上例中,可以改为这样
效果是一样的
而且,这种写法,更适合 属性后面直接跟匿名函数,像这样
document.querySelector('#salary').onkeydown = function (event){
if (event.ctrlKey && event.key==='Enter') {
document.querySelector('pre').innerText = `处理结果省略...`
}
}
或者箭头函数,像这样
document.querySelector('#salary').onkeydown = event =>{
if (event.ctrlKey && event.key==='Enter') {
document.querySelector('pre').innerText = `处理结果省略...`
}
}
html 内联定义 - 不推荐
还可以直接在 元素属性中设置事件处理
,比如
<body>
<p>请输入员工薪资记录</p>
<textarea id="salary" rows="10" cols="50"
onkeydown="salaryStats(event)">
薛蟠 45600 25
薛宝钗 25776 23
薛宝琴 14346 18
王熙凤 30460 25
王子腾 55660 45
</textarea>
<div>
<br><br>
<span id="result" style='color:blue'>
分类结果</span> <br><br>
<pre>
</pre>
<div>
<script>
function salaryStats(event){
if (event.ctrlKey && event.key==='Enter') {
document.querySelector('pre').innerText = `处理结果省略...`
}
}
</script>
</body>
其中 textarea 里面的 onkeydown="salaryStats(event)"
就定义了当 keydown事件发生时,执行 salaryStats(event)
但是 很多人 不推荐这种写法,认为这样破坏了html界面和js代码的分离,不方便维护
事件针对的元素
大家应该可以理解,针对哪个元素dom对象调用 addEventListener 方法, 就是在这个元素的范围内注册事件处理函数。
非这个元素内发生的 注册事件 ,不会触发调用。
比如,上面的例子中,如果焦点在textarea输入框外 ,按 Ctrl + 回车
,不会触发调用。
可以在整个DOM范围内注册事件监听, 对整个网页都是有效的,如下
代码在html的位置
注意,上例中 <script>
元素包含的代码 是放在 body 的最后的,
如果放在head里面,像这样
<!DOCTYPE html>
<html>
<head>
<script>
document.querySelector('#salary').onkeydown = event =>{
if (event.ctrlKey && event.key==='Enter') {
document.querySelector('pre').innerText = `处理结果省略...`
}
}
</script>
</head>
<body>
<span style='color:blue'>
请输入员工薪资记录</span> <br><br>
<textarea id="salary" rows="20" cols="80">
薛蟠 45600 25
薛宝钗 25776 23
</textarea>
<div>
<br><br>
<span id="result" style='color:blue'>
分类结果</span> <br><br>
<pre>
</pre>
<div>
</body>
</html>
运行就会报错
因为 body中的script代码,执行的顺序 就是其 在html文档中的顺序。
在head中的js代码在网页内容(也就是body中的内容)渲染前,会被先执行。
这样 DOM 里面内容还没有创建, 还没有body节点,更加没有id为salary的节点对象。
document.querySelector('#salary')
值为 null
所以会报错
但是很多人喜欢把js内嵌的代码都集中放在head里面,
怎么办呢?
看下一节内容
页面加载后才执行
页面资源完成全部加载,包括页面HTML所有DOM对象产生,界面渲染完成,引用的外部js、css、图片加载完成 等等,会发出load事件
我们经常需要在页面资源完成全部内容的加载,立即执行一段代码
可以这样写
参数event 就是 load事件对象
如果不需要处理该对象,可以忽略,像这样
也可以使用 window对象的onload属性
这样,我们就可以解决前面的问题了
<!DOCTYPE html>
<html>
<head>
<script>
window.onload = () => {
document.querySelector('#salary').onkeydown = event =>{
if (event.ctrlKey && event.key==='Enter') {
document.querySelector('pre').innerText = `处理结果省略...`
}
}
};
</script>
</head>
<body>
<span style='color:blue'>
请输入员工薪资记录</span> <br><br>
<textarea id="salary" rows="20" cols="80">
薛蟠 4560 25
薛宝钗 35776 23
薛宝琴 14346 18
王熙凤 24460 25
王子腾 55660 45
</textarea>
<div>
<br><br>
<span id="result" style='color:blue'>
分类结果</span> <br><br>
<pre>
</pre>
<div>
</body>
</html>
这样,虽然键盘事件处理代码是放在head中,
但不是立即执行,而是等页面加载完成后再执行,
document.querySelector('#salary')
自然就可以找到该元素了。
事件对象和类型
我们前面代码
这个里面的 event 参数对应的就是,事件发生时,浏览器传入回调我们的函数时,传入的 事件对象
不同的用户操作触发的事件对应的事件对象的类型不同
比如我们上面的是键盘事件,对应的就是 键盘事件(KeyboardEvent)
对象类型
如果是鼠标按钮点击操作,对应的就是 鼠标事件(MouseEvent)
对象类型
不同类型的对象,其属性、方法 不同
比如 上例中,我们是 键盘事件,传入的是键盘事件对象,它的有属性
- ctrlKey
如果事件发生时,ctrl键按下,值为true,否则为false
- altKey
如果事件发生时,alt键按下,值为true,否则为false
- Shift
如果事件发生时,shift键按下,值为true,否则为false
- key
返回 事件发生时,按下按键的字符串表示,比如
Enter 对应回车键
1、2、3、4 对应数字键 1、2、3、4
a、b、c、d 对应字母键 a、b、c、d
A、B、C、D 对应字母键 A、B、C、D
等等
大家可以通过 在代码中加上
查看你的按键对应的到底是什么key属性的值
KeyboardEvent具体属性方法,可以参考MDN文档
事件对象类型 有很多,除了 键盘事件、鼠标按钮事件 外,还有 滚轮事件(WheelEvent)、拖拽事件(DragEvent)、游戏触控板事件(GamepadEvent) 等等。
还有的事件不是用户操作触发的,比如 页面加载完成事件、网址hash更改事件(HashChangeEvent)、websocket网络消息事件、 存储事件 等等
详细的事件分类,可以点击这里查看MDN文档
大家可以在需要使用 某种事件对象时,查阅该文档。
事件处理顺序
当我们操作界面元素触发事件时,比如点击下图中 深蓝色的 td,
td对象被点击事件,同时也是所有的上层元素被点击的事件。
这个道理就像: 一个南京人奥运夺冠事件,也就是一个江苏人奥运夺冠事件,也就是一个中国人奥运夺冠事件。
那么 如果我们代码 针对td和其上层元素都注册了点击处理函数,执行次序究竟是怎样的呢?
现代浏览器基本是这样做的:
点击下图中 深蓝色的 td,导致
-
浏览器创建一个 click 事件对象
-
这个事件对象会先从 浏览器DOM 顶层的 window 对象一直 传递下去,直到
触发事件的对象的父对象
,这个过程称之为capture Phase(捕获阶段)
这个路径上,如果有任何DOM对象注册了点击处理事件,就会按照从上到下的先后顺序,依次被调用
-
然后,这个事件对象 到达触发事件的td对象,这个过程称之为
target phase(目标阶段)
-
然后,这个事件对象 再从触发事件的td对象,一直传递到顶层的window对象,这个过程称之为
bubbling Phase(冒泡阶段)
注意, click 事件是会 冒泡传播 的, 但是也有些类型的事件(比如,blur、focus)是不会冒泡的,到target 位置就结束了。
不bubbling的事件具体是哪些,可以点击参考这里
要声明注册的处理函数是在 捕获阶段 触发执行
,应该这样
第3个参数如果是boolean 并且设置为true ,就表示是 Capture Phase触发行。
要声明注册的处理函数是在 非捕获阶段(target phase 和 bubbling Phase)触发执行
,应该没有第3个参数,或者第3个参数为false,如下
element.addEventListener("click", e => {这里是处理代码})
// 或者这样
element.addEventListener("click", e => {这里是处理代码}, false)
看下面的代码
<div id='outer' style='width:10rem;height:10rem;border:1px solid black'>
外层
<div id='inner' style='width:6rem;height:6rem;border:1px solid black'>
内层
</div>
</div>
<br><br>
<script>
document.querySelector('#inner')
.addEventListener("click", e => alert('处理 inner'))
document.querySelector('#outer')
.addEventListener("click", e => alert('处理 outer'))
document.querySelector('body')
.addEventListener("click", e => alert('处理 body'))
</script>
点击内层元素,就会发现alert次序是
如果改为
<div id='outer' style='width:10rem;height:10rem;border:1px solid black'>
外层
<div id='inner' style='width:6rem;height:6rem;border:1px solid black'>
内层
</div>
</div>
<br><br>
<script>
document.querySelector('#inner')
.addEventListener("click", e => alert('处理 inner'),true)
document.querySelector('#outer')
.addEventListener("click", e => alert('处理 outer'),true)
document.querySelector('body')
.addEventListener("click", e => alert('处理 body'),true)
</script>
点击内层元素,就会发现日志结果是
如果改为
<div id='outer' style='width:10rem;height:10rem;border:1px solid black'>
外层
<div id='inner' style='width:6rem;height:6rem;border:1px solid black'>
内层
</div>
</div>
<br><br>
<script>
document.querySelector('#inner')
.addEventListener("click", e => alert('处理 inner'))
document.querySelector('#outer')
.addEventListener("click", e => alert('处理 outer'))
document.querySelector('body')
.addEventListener("click", e => alert('处理 body'),true)
</script>
点击内层元素,就会发现次序
事件对象 target属性 / this
当我们实习事件处理函数的时候,传入的参数对象就是 触发的事件对象,这里我们用变量名 e
指代它
这个事件对象的属性中 有两个要注意的:
e.target
指代了 真正触发事件的那个DOM对象
而
e.currentTarget
指代了当前 正在处理事件的DOM对象, 也就是当前处理函数 注册对应那个对象
看一个例子
<body>
<div id='outer' style='width:10rem;height:10rem;border:1px solid black'>
外层
<div id='inner' style='width:6rem;height:6rem;border:1px solid black'>
内层
</div>
</div>
<br>
<button onclick='location.reload()'>重置</button>
<script>
function changeColor(e) {
e.target.style.backgroundColor = 'green'
e.currentTarget.style.backgroundColor = 'gray'
}
document.querySelector('#outer')
.addEventListener("click", changeColor )
</script>
</body>
如果你点击内层框,会发现
内层框(真正触发事件的元素,也就是e.target )变为绿色 green
外层框(注册监听事件的元素,也就是e.currentTarget )变为灰色 gray
注意:注册监听事件的元素的内部元素触发的该事件,也会上升到
如果你点击外层框,
由于外层框 既是注册监听事件的元素,也是 真正触发事件的元素
所以两行代码都指代它,那么后面一行代码的效果会最终生效,就是外层框变成了灰色。
内层框没有被涉及到,颜色保持透明,显示的是外层框的颜色。
在处理函数中,也可以使用 this
,等价于 event.currentTarget
比如,上面的代码可以等价改为