Appearance
JavaScript教程 - 16 DOM
DOM 继续!
16.12 事件
重新来说回事件。
1 事件对象
什么是事件对象?
浏览器在事件触发时自动创建的一个特殊对象,包含了与该事件相关的所有信息和功能(包括鼠标、键盘等信息)。例如鼠标相关的事件,会包含事件触发时候的鼠标位置。
举个栗子:
鼠标在 div 中移动,获取事件对象。
html
<body>
<div id="div1" style="width: 100px; height: 100px; background-color: lightblue;"></div>
<script>
const div1 = document.getElementById('div1');
div1.onmousemove = function (event) {
div1.innerHTML = event.x + ", " + event.y; // 可以获取事件的详细信息
};
</script>
</body>- 在上面的代码中,给 div 添加鼠标移动事件回调方法,回调方法的第一个参数就是事件对象。
- 我们可以从事件对象中获取到鼠标当前的位置,
event.x和event.y表示鼠标相对于浏览器窗口的坐标,event.x和event.y也可以使用clientX和clientY。
运行如下:

同样,通过箭头函数或者 addEventListener 事件监听的方式也可以获取。
js
div1.onmousemove = event => {
div1.innerHTML = event.x + ", " + event.y;
}
div1.addEventListener("mousemove", function () {
div1.innerHTML = event.x + ", " + event.y;
});
div1.addEventListener("mousemove", event => {
div1.innerHTML = event.x + ", " + event.y;
});2 事件的常用属性
不同的事件,事件对象是不同的,但是它们都继承自 Event 对象。
html
<body>
<div id="div1" style="width: 100px; height: 100px; background-color: lightblue; margin: 100px;"></div>
<script>
const div1 = document.getElementById('div1');
div1.onclick = function (event) {
console.log(event.offsetX + ', ' + event.offsetY); // 鼠标相对于事件目标元素的坐标
console.log(event.x + ', ' + event.y); // 鼠标相对于浏览器窗口的坐标
console.log(event.screenX + ', ' + event.screenY); // 鼠标相对于屏幕的坐标
console.log(event.pageX + ', ' + event.pageY); // 鼠标相对于整个文档的坐标(包含滚动)
};
</script>
</body>- 上面通过一些方式获取到了鼠标相对于不同对象的位置。
3 事件冒泡
什么是事件冒泡?
事件冒泡是指当 DOM 元素上的事件被触发时,这个事件会从该元素向上传播到它的父元素、祖先元素,直到 document 根节点。
例如,div1 > div2 > div3 三个 div 嵌套,当点击 div3 的时候,会触发 div3 的点击事件,然后点击事件会向上传播给 div2,然后继续传播给 div1 。
演示一下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>For技术栈</title>
<style>
#div1 {
width: 200px;
height: 200px;
background-color: lightblue;
}
#div2 {
width: 150px;
height: 150px;
background-color: lightgreen;
}
#div3 {
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
<script>
const div1 = document.getElementById('div1');
const div2 = document.getElementById('div2');
const div3 = document.getElementById('div3');
div1.addEventListener('click', ) = function (event) {
alert('点击div1');
};
div2.onclick = function (event) {
alert('点击div2');
};
div3.onclick = function (event) {
alert('点击div3');
};
</script>
</body>
</html>显示如下:

当点击 div3 的时候,会依次弹出 点击div3 、点击div2、点击div1 ,一次传递给父元素。
需要注意,事件的冒泡,和事件的订阅是没有关系的,div3没有订阅点击事件,事件也是会传递给 div2 和 div1 的。另外和 CSS 样式无关,div3 在 div2 中,即使通过 CSS 样式,让 div3 显示到 div2 的外面,div3 的样式还是会传递给 div2 和 div1,因为从 HTML 结构上它们是父子关系。
事件的冒泡对于我们的开发是有利的,会简化我们的开发。
当然,我们也可以在 div3 的点击事件中,取消点击事件的冒泡,这样 div2 和 div1 就收不到事件了。
举个例子:
js
div3.onclick = function (event) {
alert('点击div3');
event.stopPropagation(); // 停止事件冒泡
};- 在 div3 的点击事件处理函数中,可以通过
event.stopPropagation();停止事件的冒泡。
4 触发事件的对象
在事件的回调函数中,我们可以获取到触发事件的对象,还可以获取绑定事件的对象。
举个栗子:
如果事件是从 div3 传递给 div1 的,那么在 div1 的回调函数中
js
div1.onclick = function (event) {
console.log(event.target); // 触发事件的对象<div id="div3">
console.log(this); // 绑定事件的对象,<div id="div1">
console.log(event.currentTarget); // 绑定事件的对象,同this <div id="div1">
};event.target表示当前触发事件的对象,事件是 div3 触发的传递给 div1 的,那么这里 target 还是 div3。this(非箭头函数)表示绑定事件的对象,这里是 div1,event.currentTarget表示绑定事件的对象,所以和 this 相同,如果在 箭头函数中,没有办法获取到 this,那么可以使用event.currentTarget获取到绑定事件的对象。- 如果在 div3 的事件回调函数中,那么
event.target、this、event.currentTarget是相同的。
5 阻止默认行为
<a> 标签在点击的时候,会跳转到 href 属性指定的地址,在讲解 HTML5 的时候说过,可以通过如下方式阻止 <a> 标签的默认行为:
html
<a href="javascript:void(0);">点击我什么都不会发生</a>
<!-- 或者 -->
<a href="javascript:;">点击我什么都不会发生</a>有时候我们需要通过监听 <a> 的点击事件,做一些处理,那么可以像上面这样禁用 <a> 标签的默认行为。
除了上面这种方式,我们还可以在 <a> 标签的点击事件中,通过 event.preventDefault(); 阻止 <a> 标签的默认行为:
html
<a id="my-a" href="https://www.foooor.com">For技术栈</a>
<script>
const myA = document.getElementById('my-a');
myA.onclick = function(event) {
event.preventDefault(); // 阻止默认行为
alert('点击了a标签');
};
</script>6 事件的委派
什么是事件的委派?
事件委派(Event Delegation)是 JavaScript 中的一种事件处理技巧,它利用事件冒泡,把多个子元素的事件,统一交给父元素来监听和处理,从而减少事件绑定数量、提高性能和灵活性。
举个栗子:
查看一下下面的两种事件绑定方式。
html
<body>
<ul id="list">
<li>苹果</li>
<li>香蕉</li>
<li>橘子</li>
</ul>
<script>
// 方式一:传统方式
const liItems = document.querySelectorAll("#list li");
// 遍历liItems,给每个li添加点击事件
liItems.forEach((li) => {
li.addEventListener("click", () => {
console.log("你点击了", li.textContent);
});
});
// 方式二:事件委派方式
const listEle = document.getElementById("list");
// 给ul添加点击事件
listEle.addEventListener("click", (e) => {
// 将liItems转换为数组,判断点击的是否是数组中的li
const liArray = Array.from(liItems);
if (liArray.includes(e.target)) {
console.log("你点击了", e.target.textContent);
}
});
</script>
</body>- 在上面的代码中,方式一是给所有的元素都绑定事件;
- 方式二是将元素的绑定事件委托给父元素,当点击子元素的时候,会通过事件冒泡传递给父元素,然后在判断点击的是哪个子元素,这样就减少了事件的绑定数量。
如果我们此时向父元素中动态的添加子元素,通过第二种事件委派的方式,就不需要动态添加的元素添加绑定事件,从而简化了处理。
7 事件的传播机制
事件的传播其实是经历三个阶段的:
捕获阶段:事件从最外层(
window/document)开始,也就是事件一开始是由祖先元素捕获,然后从祖先元素依次向子元素传播,直到传播到目标元素。目标阶段:事件到达真正的目标元素,此阶段事件会在目标元素本身触发。
冒泡阶段:事件从目标元素开始,逐层向上传播回祖先元素,直到
document。
也就是事件是由祖先元素捕获,然后传播给目标元素,然后再由目标元素逐层向上传播回祖先元素。
我们在监听事件进行处理的时候,默认是在冒泡阶段触发的,可以通过监听事件的第三个参数,将触发阶段修改到捕获阶段。
举个栗子:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>For技术栈</title>
<style>
#div1 {
width: 200px;
height: 200px;
background-color: lightblue;
}
#div2 {
width: 150px;
height: 150px;
background-color: lightgreen;
}
#div3 {
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
<script>
const div1 = document.getElementById('div1');
const div2 = document.getElementById('div2');
const div3 = document.getElementById('div3');
div1.addEventListener('click', function (event) {
alert('点击div1');
}, true);
div2.addEventListener('click', function (event) {
alert('点击div2');
}, true);
div3.addEventListener('click', function (event) {
alert('点击div3');
}, true);
</script>
</body>
</html>- 上面通过设置
addEventListener第三个参数为 true,让事件在捕获的阶段触发,这样在点击 div3 的时候,会依次弹出点击div1 > 点击div2 > 点击div3。 - 在捕获阶段,也同样可以通过
event.stopPropagation();方法阻止事件的传播。
一般情况下,我们是不需要修改的,除非有特殊需求。
8 常用事件
鼠标事件
可绑定几乎所有可视元素,如按钮、图片、div、a 标签等。
| 事件名 | 描述 | 示例代码 |
|---|---|---|
click | 单击(左键) | button.addEventListener("click", fn) |
dblclick | 双击 | div.addEventListener("dblclick", fn) |
mousedown | 鼠标按下 | div.addEventListener("mousedown", fn) |
mouseup | 鼠标释放 | |
mousemove | 鼠标移动 | 用于绘图、拖动 |
mouseenter | 鼠标进入(不冒泡) | 类似 mouseover,但不涉及子元素 |
mouseleave | 鼠标离开(不冒泡) | |
mouseover | 鼠标移入(会冒泡) | 用于提示、浮层 |
mouseout | 鼠标移出(会冒泡) | |
contextmenu | 鼠标右键 | 可自定义右键菜单 |
举个栗子:
js
document.querySelector("button").addEventListener("click", () => {
alert("按钮被点击了");
});键盘事件
默认可以绑定可以获取到焦点的元素,例如<input>、<textarea>、<button> 和 document 或 window 对象。如果是非表单元素,例如 div,需添加 tabindex="0" 属性才可以获得焦点,然后可以接收键盘事件。
| 事件名 | 描述 | 注意事项 |
|---|---|---|
keydown | 按键按下(包括功能键) | 最常用,支持组合键 |
keyup | 按键释放 | 可用于监听输入完成 |
举个栗子:
js
document.addEventListener("keydown", (e) => {
console.log(e.key + ", " + e.keyCode); // 按下按键会打印按键名字和按键的号码
});
document.addEventListener("keydown", (e) => {
if (e.ctrlKey && e.key === "s") { // 同时按下ctrl+s
e.preventDefault();
alert("保存命令被拦截");
}
});表单事件
可绑定表单类元素,如 <form>、<input>、<textarea>、<select>
| 事件名 | 描述 | 注意事项 |
|---|---|---|
submit | 表单提交 | 必须阻止默认提交:e.preventDefault() |
reset | 表单重置 | |
change | 内容改变(失去焦点时才触发) | 比如输入框输入后点到别处才触发 |
input | 实时输入(每输入一次就触发一次) | 用于动态校验、计数 |
focus | 获取焦点 | 不会冒泡 |
blur | 失去焦点 | 不会冒泡 |
举个栗子:
阻止表单提交:
js
document.querySelector("form").addEventListener("submit", function (e) {
e.preventDefault();
alert("提交被拦截");
});窗口与文档事件
可绑定元素:window、document
| 事件名 | 描述 |
|---|---|
load | 页面资源(包括图片)全部加载完 |
DOMContentLoaded | 仅 DOM 结构加载完成(推荐) |
resize | 浏览器窗口大小变化 |
scroll | 页面或元素滚动 |
举个例子:
js
window.addEventListener("scroll", function (e) {
console.log("滚动")
});剪贴板事件
可绑定任何可编辑元素,如 input、textarea,或使用 contenteditable="true" 的元素
| 事件名 | 描述 |
|---|---|
copy | 复制时触发 |
cut | 剪切时触发 |
paste | 粘贴时触发 |
举个栗子:
禁止粘贴:
js
input.addEventListener("paste", (e) => {
e.preventDefault();
alert("禁止粘贴!");
});下面来做两个练习。
16.13 学生信息表
实现一个能添加和删除学生信息的表格,显示如下:

实现思路:
给添加按钮绑定点击事件,点击按钮的时候,获取到姓名和年龄,然后创建 <tr> 元素添加到表格的 <tbody> 中即可。 <tr> 元素中需要创建三个 <td>,在第三个 <td> 中添加删除按钮,并绑定点击事件,用于删除 <tr> 。
完整代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>学生信息表</title>
<style>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
table {
margin-top: 10px;
min-width: 600px;
}
th, td {
padding: 5px 10px;
}
</style>
</head>
<body>
<h2>学生信息表</h2>
姓名:<input type="text" id="name" />
年龄:<input type="number" id="age" />
<button id="addBtn">添加</button>
<table id="studentTable">
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 动态插入的学生行在这里 -->
</tbody>
</table>
<script>
// 名字输入框
const nameInput = document.getElementById("name");
// 年龄输入框
const ageInput = document.getElementById("age");
// 添加按钮
const addBtn = document.getElementById("addBtn");
// 表格的body
const tbody = document.querySelector("#studentTable tbody");
// 给添加按钮绑定点击事件
addBtn.addEventListener("click", function () {
// 获取到输入框的内容,并去掉前后的空格
const name = nameInput.value.trim();
const age = ageInput.value.trim();
if (!name || !age) {
alert("请输入完整信息!");
return;
}
// 创建一行tr
const tr = document.createElement("tr");
// 创建姓名单元格
const tdName = document.createElement("td");
tdName.textContent = name;
// 创建年龄单元格
const tdAge = document.createElement("td");
tdAge.textContent = age;
// 创建操作单元格和删除按钮
const tdOp = document.createElement("td");
const delBtn = document.createElement("button");
delBtn.textContent = "删除";
// 给按钮添加绑定事件
delBtn.addEventListener("click", () => {
tbody.removeChild(tr); // 删除tr
});
tdOp.appendChild(delBtn);
// 添加单元格到行中
tr.appendChild(tdName);
tr.appendChild(tdAge);
tr.appendChild(tdOp);
// 添加行到表格中
tbody.appendChild(tr);
// 清空输入框
nameInput.value = "";
ageInput.value = "";
});
</script>
</body>
</html>16.14 拖拽
创建一个 div,在页面上可以随意拖拽。
如下图:

实现思路:
当鼠标按下时,记录鼠标相对于元素的偏移量,在移动的时候,设置鼠标也是在元素的相同位置;
鼠标移动,元素跟随鼠标移动;
鼠标抬起时,完成拖拽。
代码如下:
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>拖拽练习</title>
<style>
#drag-box {
width: 100px;
height: 100px;
background-color: lightblue;
position: absolute; /* 关键 */
top: 100px;
left: 100px;
}
</style>
</head>
<body>
<div id="drag-box">拖我</div>
<script>
const box = document.getElementById('drag-box');
let isDragging = false;
let offsetX = 0;
let offsetY = 0;
box.addEventListener('mousedown', function (e) {
isDragging = true;
// 计算鼠标点击位置相对 div 的偏移,用于拖拽的准确位置
offsetX = e.clientX - box.offsetLeft;
offsetY = e.clientY - box.offsetTop;
// 防止选中文字
e.preventDefault();
});
document.addEventListener('mousemove', function (e) {
if (isDragging) {
// 实时修改div的位置
box.style.left = (e.clientX - offsetX) + 'px';
box.style.top = (e.clientY - offsetY) + 'px';
}
});
document.addEventListener('mouseup', function () {
isDragging = false;
});
</script>
</body>
</html>内容未完......