关联:
目录
DOM 事件
事件概述
1. 事件
可以感知用户操作和浏览器行为, 然后做出相应的回应,如下图就是一个用户点击事件

2. 事件的 3 要素:
① 事件源(触发事件的元素)
② 事件类型(单击事件、双击事件、加载事件等)
③ 事件处理程序(对事件做出的响应)
3. 事件对象
当事件发生时会创建一个事件对象(Event 对象), 它包含事件源、事件类型、鼠标位置、页面位置等信息。
直接在事件处理函数的参数列表中声明形参, 就可以获取事件对象
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
<style>
div{
width: 100px;
height: 100px;
border:1px solid black;
}
</style>
</head>
<body>
<div></div>
</body>
<script>
let div = document.querySelector('div')
div.onclick = function(e){
console.log(e instanceof Event) // 结果: true
}
</script>
</html>
事件对象常用属性:
| 属性名 | 说明 |
| target | 触发事件的元素对象 |
| clientX | 鼠标在可视窗口的 x 轴坐标 不受滚动条滑动影响 |
| clientY | 鼠标在可视窗口的 y 轴坐标 不受滚动条滑动影响 |
| pageX | 鼠标在 html 文档页的 x 轴坐标 受滚动条滑动影响 |
| pageY | 鼠标在 html 文档页的 y 轴坐标 受滚动条滑动影响 |
| screenX | 鼠标在电脑屏幕的 x 轴坐标 |
| screenY | 鼠标在电脑屏幕的 y 轴坐标 |
注册事件
注册事件有多种方式, 下面举例用不同的方式实现如下相同的效果

1. 标签内注册方式
写法简单, 但是同一元素的同一事件只能指定一个事件处理程序
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
<style>
div {
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
border: 1px solid black;
}
</style>
<script>
function changeColor() {
let obj = document.querySelector('div')
obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
}
</script>
</head>
<body>
<div onclick="changeColor()">点击变色</div>
</body>
</html>
步骤:
1. 声明事件处理函数 changeColor()
2. 标签内指定事件类型( onclick )和事件处理函数( changeColor() )
2. 传统注册方式
和标签内注册的方式一样, 同一元素的同一事件也只能指定一个事件处理程序
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
<style>
div {
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
border: 1px solid black;
}
</style>
</head>
<body>
<div>点击变色</div>
<script>
let obj = document.querySelector('div')
obj.onclick = function () {
obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
}
</script>
</body>
</html>
步骤:
1. 获取标签元素对象
2. 为其指定事件和事件处理程序
3. 注册监听方式
可以对同一元素的同一事件添加多个处理程序, 但是有浏览器兼容问题, 低版本 IE 就不支持该方式
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
<style>
div {
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
border: 1px solid black;
}
</style>
</head>
<body>
<div>点击变色</div>
<script>
let obj = document.querySelector('div')
obj.addEventListener('click', function () {
obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
})
obj.addEventListener('click', function () {
alert('可以对同一元素的同一事件添加多个处理程序')
})
</script>
</body>
</html>
步骤:
1. 获取标签元素对象
2. 为其指定事件和事件处理程序(指定事件时, 不需要写 on,比如 onclick、ondblclick 写成 click、dblclick 就可以)
兼容低版本 IE 的写法, 个人不建议兼容低版本IE, 太老的浏览器很多语法都不支持, 难道为了兼容都不用了么?
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
<style>
div {
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
border: 1px solid black;
}
</style>
</head>
<body>
<div>点击变色</div>
<script>
var obj = document.getElementsByTagName('div')[0]
var process = function () {
obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
}
// 注册事件兼容写法
if (obj.addEventListener) {
obj.addEventListener('click', process)
} else {
obj.attachEvent('onclick', process)
}
</script>
</body>
</html>
步骤:
1. 判断浏览器是否支持 addEvenetListener
2. 支持:使用 addEvenetListener 注册事件,注意事件类型不用写 on
3. 不支持:使用 attachEvent 注册事件,注意事件类型必须写 on
删除事件
1. 标签内注册方式和传统注册方式的删除
设置指定事件类型的事件处理程序为 null 即可
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
<style>
div {
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
border: 1px solid black;
}
</style>
</head>
<body>
<div onclick="changeColor()">点击变色</div>
<script>
let obj = document.querySelector('div')
function changeColor() {
obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
}
// 删除注册的事件
obj.onclick = null;
</script>
</body>
</html>
2. 注册监听方式的删除
① 注册时, 设置的指定事件类型的事件处理程序, 不能是匿名函数
② 使用 removeEventListener( 注册时的事件类型, 注册时的函数对象地址 ) 方法移除注册的事件监听
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
<style>
div {
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
border: 1px solid black;
}
</style>
</head>
<body>
<div>点击变色</div>
<script>
let obj = document.querySelector('div')
// 声明函数变量
let process = function () {
obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
}
// 注册事件监听
obj.addEventListener('click', process)
// 移除事件监听
obj.removeEventListener('click', process)
</script>
</body>
</html>
事件的捕获与冒泡
1. 事件流
事件发生时,JS引擎会找到目标区域内的元素及元素间的关系, 如下图:

事件流的知识点:
1. 事件是具备传播性的, 传播路径就是沿着这条 DOM 树, 先自上而下, 再自下而上的往返一次。
2. 自上而下的传播阶段称为捕获阶段,自下而上的传播阶段称为冒泡阶段。
3. 传播时, 每经过一个元素就会判断该元素是否设置了相同事件, 且是否可以在该阶段被触发。
4. 元素的事件只能在一个阶段内被触发, 要么捕获阶段触发, 要么冒泡阶段触发。
2. 冒泡阶段
1. 冒泡阶段是事件的默认执行阶段
2. 手动设置冒泡阶段执行:使用注册监听的方式添加事件 addEventListener, 第三个参数传 false
3. resize scroll focus blur mouseenter mouseleave load unload media 不支持冒泡阶段
效果:

代码结构:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
</head>
<body>
<div>
<p>点我事件冒泡</p>
</div>
<script>
let html = document.documentElement
let body = document.body
let div = document.querySelector('div')
let p = document.querySelector('p')
html.onclick = () => {
alert('html')
}
body.addEventListener('click', () => {
alert('body')
}, false)
div.addEventListener('click', () => {
alert('div')
})
p.onclick = () => {
alert('p')
}
</script>
</body>
</html>
理解代码中的事件流过程:
先执行捕获阶段
html (冒泡可触发) → body (冒泡可触发) → div (冒泡可触发) → p (冒泡可触发)
在捕获阶段没有可以被触发的事件, 然后在执行冒泡阶段
p (冒泡可触发) → div (冒泡可触发) → body (冒泡可触发) → html (冒泡可触发)
所有的元素都可以在冒泡阶段触发事件, 所以会按顺序执行他们的事件处理程序。
3. 捕获阶段
设置捕获阶段执行:使用注册监听的方式添加事件 addEventListener, 第三个参数传 true
效果:

代码结构:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
</head>
<body>
<div>
<p>点我事件冒泡</p>
</div>
<script>
let html = document.documentElement
let body = document.body
let div = document.querySelector('div')
let p = document.querySelector('p')
html.addEventListener('click', () => {
alert('html')
}, true)
body.addEventListener('click', () => {
alert('body')
}, true)
div.addEventListener('click', () => {
alert('div')
}, true)
p.addEventListener('click', () => {
alert('p')
}, true)
</script>
</body>
</html>
理解代码中的事件流过程:
先执行捕获阶段
html (捕获可触发) → body (捕获可触发) → div (捕获可触发) → p (捕获可触发)
所有的元素都可以在捕获阶段触发事件, 所以会按顺序执行他们的事件处理程序
然后在执行冒泡阶段
p (捕获可触发) → div (捕获可触发) → body (捕获可触发) → html (捕获可触发)
在冒泡阶段没有可以被触发的事件。
4. 加深理解 - 元素结构中捕获阶段和冒泡阶段混合存在
例子中, 目标区域内的元素, 既有捕获阶段执行的又有冒泡阶段执行的, 帮助加深理解。
效果:

代码结构:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
</head>
<body>
<div>
<p>点我事件冒泡</p>
</div>
<script>
let html = document.documentElement
let body = document.body
let div = document.querySelector('div')
let p = document.querySelector('p')
html.addEventListener('click', () => {
alert('html')
}, true)
body.addEventListener('click', () => {
alert('body')
}, false)
div.addEventListener('click', () => {
alert('div')
}, true)
p.addEventListener('click', () => {
alert('p')
}, false)
</script>
</body>
</html>
理解代码中的事件流过程:
先执行捕获阶段
html (捕获可触发) → body (冒泡可触发) → div (捕获可触发) → p (冒泡可触发)
该阶段中事件会执行的元素有 html 和 div , 然后在执行冒泡阶段
p (冒泡可触发) → div (捕获可触发) → body (冒泡可触发) → html (捕获可触发)
会执行事件的元素有 p 和 body,所以最后的执行顺序为 html → div → p → body
5. 事件流常见使用技巧 - 事件委托
事件委托就是多个同根元素间, 事件和事件处理程序都相同时, 可以利用冒泡的特性, 只对他们的父节点元素设置事件即可, 而不需要每个子元素都单独设置。
一般常用的场合就是列表, 如下图效果:

实现上图效果有三种常见的方式:
① 最笨的方式 - 硬编码。 为每个元素依次添加事件,但是不够灵活, 扩展麻烦
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
<style>
table {
border-collapse: collapse;
}
td, th {
width: 100px;
text-align: center;
border: 1px solid black;
}
</style>
</head>
<body>
<table id="score_table">
<thead>
<tr>
<th>序号</th>
<th>名字</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>赵</td>
<td>97</td>
<td><input type="button" value="删除"></td>
</tr>
<tr>
<td>2</td>
<td>钱</td>
<td>95</td>
<td><input type="button" value="删除"></td>
</tr>
<tr>
<td>3</td>
<td>孙</td>
<td>99</td>
<td><input type="button" value="删除"></td>
</tr>
<tr>
<td>4</td>
<td>李</td>
<td>98</td>
<td><input type="button" value="删除"></td>
</tr>
</tbody>
</table>
</body>
<script>
// 获取元素对象
let scoreTBody = document.querySelector('#score_table tbody')
let btnFirst = document.querySelector('#score_table tr:first-of-type input[type=button]')
let btnSecond = document.querySelector('#score_table tr:nth-of-type(2) input[type=button]')
let btnThird = document.querySelector('#score_table tr:nth-of-type(3) input[type=button]')
let btnLast = document.querySelector('#score_table tr:last-of-type input[type=button]')
// 声明点击事件处理程序
let process = (e) => {
scoreTBody.removeChild(e.target.parentElement.parentElement)
}
// 依次设置点击事件
btnFirst.addEventListener('click', process)
btnSecond.addEventListener('click', process)
btnThird.addEventListener('click', process)
btnLast.addEventListener('click', process)
</script>
</html>
② 常见方式 - 遍历。获取元素集合,通过遍历添加事件, 但是当列表内容过多时, 这种方式会影响页面性能。
将上面 script 部分替换为下面代码
<script>
// 获取元素集合
let scoreTBody = document.querySelector('#score_table tbody')
let btnArray = document.querySelectorAll('#score_table tr td input[type=button]')
// 遍历添加事件
for (let i = 0; i < btnArray.length; i++) {
btnArray[i].addEventListener('click', (e) => {
scoreTBody.removeChild(e.target.parentElement.parentElement)
})
}
</script>
③ 最好的方式 - 事件委托。利用冒泡原理,只给外层元素设置事件即可。
替换 script 代码为
<script>
// 获取外层元素对象并设置事件
let scoreTBody = document.querySelector('#score_table tbody')
scoreTBody.addEventListener('click', (e) => {
scoreTBody.removeChild(e.target.parentElement.parentElement)
})
</script>
禁止默认事件
1. 禁止捕获或冒泡
禁止捕获: e.stopPropagation() 写在上游元素中, 且该元素的事件允许捕获阶段触发
禁止冒泡: e.stopPropagation() 写在下游元素中, 且该元素的事件允许冒泡阶段触发
效果:

2. 禁止链接跳转事件
① 单纯禁止跳转行为
效果:

代码:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
</head>
<body>
<a href="javascript:void(0)">阻止链接跳转 javascript:void(0) 方式</a><br>
<a href="javascript:">阻止链接跳转 javascript: 方式</a>
</body>
</html>
② 禁止跳转行为, 并附加自定义功能
效果:

代码:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
</head>
<body>
<a href="http://www.baidu.com">点我不会跳转, 并且有自己的功能</a>
</body>
<script>
let a = document.querySelector('a')
a.addEventListener('click', (e) => {
alert('弹出框')
e.preventDefault()
})
</script>
</html>
3. 禁止弹出右键菜单
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
</head>
<body></body>
<script>
document.addEventListener('contextmenu', function (e) {
e.preventDefault();
})
</script>
</html>
DOM 操作的代码书写位置
因为代码是按顺序执行, 当还没渲染完 DOM 元素, 就用 JS 操作该 DOM 时, 会获取 DOM 对象失败,所以 JS 操作 DOM 的代码的书写位置很重要。
JS 写在欲操作的元素代码之前,会获取元素失败, 因为还未渲染就要获取
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
</head>
<body>
<script>
console.log(document.querySelector('#container')) // 结果:null
</script>
<div id="container"></div>
</body>
</html>
JS 代码写在欲操作元素的后面, 可以成功获取元素, 因为元素已经渲染完成
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
</head>
<body>
<div id="container"></div>
<script>
console.log(document.querySelector('#container')) // 结果:获得元素对象成功
</script>
</body>
</html>
JS 代码写在页面加载完成事件中, 这样不管 JS 的位置写在哪里都不影响获取 DOM 对象, 因为是加载完成后才执行
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM</title>
</head>
<body>
<script>
document.addEventListener('load', () => {
console.log(document.querySelector('#container')) // 结果:获得元素对象成功
})
</script>
<div id="container"></div>
</body>
</html>
JS 常用事件列表
| 事件名 | 描述 | 事件类型 |
| onclick | 鼠标单击 | 鼠标事件 |
| ondblclick | 鼠标双击 | 鼠标事件 |
| onmousedown | 鼠标压下 | 鼠标事件 |
| onmouseup | 鼠标弹起 | 鼠标事件 |
| onmousemove | 鼠标移动 | 鼠标事件 |
| oncontextmenu | 鼠标右键 | 鼠标事件 |
| onwheel | 鼠标滚轮滑动 | 鼠标事件 |
| onmouseover | 鼠标移入 | 鼠标事件 |
| onmouseout | 鼠标移开 | 鼠标事件 |
| onmouseenter | 鼠标移入, 不支持冒泡 | 鼠标事件 |
| onmouseleave | 鼠标移开, 不支持冒泡 | 鼠标事件 |
| onkeydown | 按键压下 | 键盘事件 |
| onkeyup | 按键弹起 | 键盘事件 |
| onkeypress | 按键压下, 不支持功能键 | 键盘事件 |
| onfocus | 获得焦点 | 表单事件 |
| onblur | 失去焦点 | 表单事件 |
| onchange | 内容改变 | 表单事件 |
| onsubmit | 点击 submit 按钮 | 表单事件 |
| onreset | 点击 reset 按钮 | 表单事件 |
| onselect | 选中文字 | 表单事件 |
| onload | 页面元素加载完成 | 浏览器事件 |
| onresize | 改变浏览器窗口大小 | 浏览器事件 |
本文详细介绍了JSDOM中的节点操作,包括DOM事件的概述、事件注册(标签内、传统方式、监听方式)、删除事件、事件的捕获与冒泡阶段,以及如何禁止默认事件。此外,还探讨了DOM操作的代码书写位置,并列举了常见的JS事件列表。文章通过实例代码深入解析了事件流和事件委托的概念,有助于读者更好地理解和应用DOM事件。
1532

被折叠的 条评论
为什么被折叠?



