JavaScript事件
JavaScript 事件是指用户与网页交互时触发的行为(如点击、输入),或浏览器自身的行为(如点击、输入),或浏览器自身的行为(如加载完成)。通过事件机制,可实现网页的动态响应。
一、事件绑定(注册事件)
为元素绑定事件处理函数,使其在事件触发时执行。主要有3种方式:
1. HTML 内联绑定(不推荐)
直接在 HTML 标签中通过事件属性绑定函数,耦合度高,不利于维护。
语法:
<元素 on事件名="函数名(参数)">
示例:
<button onclick="showMsg()">点击我</button>
<script>
function showMsg() {
alert("HTML 内联绑定触发")
}
</script>
点击之后:
2.DOM 属性绑定(简单场景可用)
通过 JS 获取 DOM 元素后,直接给元素的事件属性赋值函数。
缺点:同一元素同一事件只能绑定一个函数,后绑定的会覆盖前一个。
语法:
元素.on事件名 = 函数
示例:
(先写第一个函数)
<button id="btn2">DOM属性绑定</button>
<script>
const btn2 = document.getElementById('btn2')
// 绑定事件
btn2.onclick = function() {
alert('DOM属性绑定触发')
}
</script>
(然后写第二个函数)
<button id="btn2">DOM属性绑定</button>
<script>
const btn2 = document.getElementById('btn2')
// 绑定事件
btn2.onclick = function() {
alert('DOM属性绑定触发')
}
btn2.onclick = function() {
alert('新函数覆盖旧函数')
}
</script>
点击后只会弹出一次“新函数覆盖旧函数”。
不会先弹出“DOM属性绑定触发”之后再弹出“新函数覆盖旧函数”。
3. addEventListener (推荐)
W3C 标准方法,支持给同一元素同一事件绑定多个函数,按绑定顺序执行;还能指定事件捕获 / 冒泡阶段。
语法:
元素.addEventListener(事件名,处理函数,[userCapture])
- 事件名:不含 on ,如 click、mouseover;
- 处理函数:事件触发时执行的函数(可匿名或命名);
- userCapture:可选,布尔值,true 表示在捕获阶段触发,false(默认)表示在冒泡阶段触发。
示例:
<script>
const btn3 = document.getElementById('btn3')
// 绑定第一个函数
btn3.addEventListener('click',function(){
console.log('第一个点击事件')
})
btn3.addEventListener('click',showLog)
function showLog() {
console.log('第二个点击事件')
}
</script>
点击后控制台同时出现:
二、事件解绑(移除事件)
移除已绑定的事件处理函数,避免内存泄漏或意外触发。
1. DOM 属性解绑
直接将元素的事件属性赋值为 null,即可移除通过DOM属性绑定的事件。
语法:
元素.on事件名 = null
示例:
<button id="btn4">点击后解绑</button>
<script>
const btn4 = document.getElementById('btn4')
btn4.onclick = function() {
alert('点击一次后解绑')
// 解绑事件(后续点击无效)
btn4.onclick = null
}
</script>
点击一次后弹窗,后续点击都不弹出弹窗。
2. removeEventListener(对应addEventListener)
移除通过 addEventListner 绑定的事件,必须满足3个条件:
- 事件名与绑定一致;
- 处理函数为命名函数(匿名函数无法移除);
- useCapture 与绑定一致。
语法:
元素.removeEventListener(事件名, 处理函数, [useCapture]);
示例:
<button id="btn5">绑定/解绑</button>
<script>
const btn5 = document.getElementById('btn5')
// 命名处理函数(关键:匿名函数无法移除)
function handleClick() {
console.log('事件触发')
}
// 绑定事件
btn5.addEventListener('click',handleClick)
// 1秒后自动解绑
setTimeout(() => {
btn5.removeEventListener('click',handleClick)
console.log('事件已解绑,后续点击无效')
}, 3000)
</script>
三、事件流(捕获与冒泡)
当元素触发事件时,事件会按 “捕获阶段–>目标阶段–>冒泡阶段” 传播,这就是事件流。
- 捕获阶段:事件从最顶层的 document 向下传播到目标元素。
- 目标阶段:事件到达触发事件的目标元素。
- 冒泡阶段:事件从目标元素向上传播回 document。
我们再回忆一下addEventListener 绑定事件的语法:
元素.addEventListener(事件名, 处理函数, [useCapture])
我们说过,第三个参数 useCapture 是布尔值,true表示在捕获阶段触发,false表示在冒泡阶段触发。
示例:
<div id="outer" style="padding: 20px; background-color: aquamarine;">
<div id="inner" style="padding: 20px; background-color: aqua;">
<button id="btn6">点击</button>
</div>
</div>
<script>
const outer = document.getElementById('outer')
const inner = document.getElementById('inner')
const btn6 = document.getElementById('btn6')
// 捕获阶段触发(useCapture: true)
outer.addEventListener('click',() => console.log('outer捕获'),true)
inner.addEventListener('click',() => console.log('inner捕获'),true)
// 冒泡阶段触发(useCapture: false)
outer.addEventListener('click',() => console.log('outer冒泡'),false)
inner.addEventListener('click',() => console.log('inner冒泡'),false)
btn6.addEventListener('click',() => console.log('btn目标'))
</script>
▲阻止事件冒泡
使用 event.stopPropagation( )
阻止事件向上传播,避免父元素的事件被触发。
<div id="outer" style="padding: 20px; background-color: aquamarine;">
<div id="inner" style="padding: 20px; background-color: aqua;">
<button id="btn6">点击</button>
</div>
</div>
<script>
const outer = document.getElementById('outer')
const inner = document.getElementById('inner')
const btn6 = document.getElementById('btn6')
// 捕获阶段触发(useCapture: true)
outer.addEventListener('click',() => console.log('outer捕获'),true)
inner.addEventListener('click',() => console.log('inner捕获'),true)
// 冒泡阶段触发(useCapture: false)
outer.addEventListener('click',() => console.log('outer冒泡'),false)
inner.addEventListener('click',() => console.log('inner冒泡'),false)
btn6.addEventListener('click',(event) => {
console.log('btn目标')
// 阻止冒泡到父元素
event.stopPropagation()
})
</script>
再举个例子:
<div id="parent" style="padding: 20px; background-color: aquamarine;">
父元素
<button id="btn7">点击我(不触发父元素事件)</button>
</div>
<script>
const parent = document.getElementById('parent')
const btn7 = document.getElementById('btn7')
parent.onclick = () => alert('父元素被点击')
btn7.onclick = function(event) {
alert('按钮被点击')
// 阻止事件冒泡到父元素
event.stopPropagation()
}
</script>
这是阻止事件冒泡到父元素的效果:
点击父元素,会弹出弹窗“父元素被点击”。
点击button按钮,只会弹出一次弹窗“按钮被点击”。
如果不阻止事件冒泡,会发生什么呢?
(我们一起把event.stopPropagation()
这行代码注释掉看一下效果)
结果是:
点击button按钮后,先弹出一个弹窗“按钮被点击”,然后再弹出一个弹窗“父元素被点击”。说明事件冒泡到父元素啦!
四、事件委托(事件代理)
利用事件冒泡特性,将子元素的事件统一绑定到父元素上,由父元素“代理”子元素的事件。
核心优势:
- 减少事件绑定次数,优化性能;
- 动态新增的子元素无需重新绑定事件。
实现步骤:
- 找到所有子元素的共同父元素;
- 给父元素绑定事件;
- 在事件处理函数中,通过
event.target
判断触发事件的子元素(目标元素); - 若为目标子元素,则执行对应逻辑。
语法关键:
event.target
——指向触发事件的“最具体” 的元素(子元素)。
示例(列表项点击事件委托):
<ul id="list">
<li>列表项1</li>
<li>列表项2</li>
<li>列表项3</li>
</ul>
<button id="addLi">新增列表项</button>
<script>
const list = document.getElementById('list')
const addLi = document.getElementById('addLi')
// 1.给父元素ul绑定事件(只绑定1次)
list.addEventListener('click',function(event) {
// 2.判断触发事件的是否是 li 子元素
if(event.target.tagName === 'LI') {
// 3.处理li的点击逻辑
alert('点击了'+ event.target.textContent)
}
})
// 动态新增列表项(无需重新绑定事件)
addLi.onclick = function() {
const newLi = document.createElement('li')
newLi.textContent = '新列表项' + (list.children.length + 1)
list.appendChild(newLi)
}
</script>