玩转js事件机制
js事件机制
前言
最近写了个仿饿了么的项目,其中有一个很简单的功能,就是点击一个菜品的增加按钮会让数量加一,而点击这个菜品的任何区域会跳转到这个菜品的详情页,效果图如下:


一个
小question
交给你: 点击加号按钮是不是也同时点击了这个菜品呢?那效果呈现出来的是不是两个功能会同时生效呢?
想必各位大佬心中已有答案,这也是我们要聊聊的js事件机制
问题了👀。
正文
首先,我们要知道js事件流的概念。
一、js事件流
js事件流一共分为三个阶段
:
- 从 window上 往 事件触发处 传播,遇到 注册的
捕获事件
就会触发。 - 传播到
事件触发处
,触发注册的事件。 - 从 事件触发处 往 window上 传播,遇到 注册的
冒泡事件
触发。
来张图让你更好的理解:

二、举个例子
我们要实现这个效果,定义三个容器,给三个容器都添加一个点击监听事件,点击相应的容器并输出相应的打印。我们需要观察的是点击相应的容器的输出结果。

1.第一种情况
当addEventListener
监听事件只有两个
参数时
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#greenBox{
width: 200px;
height: 200px;
background: #c2f5ca;
}
#pinkBox{
width: 100px;
height: 100px;
background-color: #f7d0d0;
color:#fff;
position: absolute;
}
#blackBox{
width: 50px;
height: 50px;
background: #000;
}
</style>
</head>
<body>
<div id="greenBox">
<div id="pinkBox">
<div id="blackBox"></div>
</div>
</div>
<script>
let greenBox=document.getElementById('greenBox')
let pinkBox=document.getElementById('pinkBox')
let blackBox=document.getElementById('blackBox')
greenBox.addEventListener('click',()=>{
console.log('greenBox')
})
pinkBox.addEventListener('click',(event)=>{
console.log('pinkBox')
})
blackBox.addEventListener('click',(event)=>{
console.log('blackBox')
})
</script>
</body>
</html>
当我们点击黑色
容器时,得到的打印结果:

这是为什么呢?
答:因为addEventListener是存在
第三个
参数的,当没有第三个参数
时,默认
是遇到注册的冒泡事件
触发。 1)当js事件流从window上往事件触发处传播的过程中,并没有遇到注册的捕获事件,故不会触发; 2)当js事件流从事件触发处往window上传播的过程中,首先遇到的是blackBox注册的的冒泡事件,之后是pinkBox,最后是greenBox。
2.第二种情况
当addEventListener监听事件有三个
参数时,第三个参数为true
或false
。当第三个参数为false
时,只在遇到注册的冒泡事件
触发;为true
时,只在遇到注册的捕获事件
触发。
1)我们在greenBox的监听事件中添加第三个参数true,代码如下:
<script>
greenBox.addEventListener('click',()=>{
console.log('greenBox')
},true)
pinkBox.addEventListener('click',(event)=>{
console.log('pinkBox')
})
blackBox.addEventListener('click',(event)=>{
console.log('blackBox')
})
</script>
当我们点击黑色容器时,得到的打印结果:

这是为什么呢?
答:因为
greenBox
的addEventListener存在的第三个
参数为true
,则说明greenBox只在遇到注册的捕获事件
触发。 1)当js事件流从window上往事件触发处传播的过程中,遇到greenBox注册的捕获事件,故先触发greenBox; 2)当js事件流从事件触发处往window上传播的过程中,首先遇到的是blackBox注册的的冒泡事件,之后是pinkBox。
2)我们在greenBox的监听事件中添加第三个参数false,在pinkBox的监听事件中添加第三个参数true,代码如下:
<script>
greenBox.addEventListener('click',()=>{
console.log('greenBox')
},flase)
pinkBox.addEventListener('click',(event)=>{
console.log('pinkBox')
},true)
blackBox.addEventListener('click',(event)=>{
console.log('blackBox')
})
</script>
当我们点击黑色容器时,得到的打印结果:

这个结果也是在意料之中的,首先pinkBox在遇到注册的的捕获事件触发,而blackBox默认在遇到注册的的冒泡事件触发,greenBox在遇到注册的的冒泡事件触发。
三、阻止默认事件
当我们想只触发其中的一个或多个
事件,而其它
事件不触发
时,我们可以使用stopPropagation()
和stopImmediatePropagation()
函数。
stopPropagation()
:终止
默认事件传播到其他容器
上
stopImmediatePropagation()
:终止
默认事件传播到其他容器
上 和自己这个容器
的其他事件
四、事件代理
事件代理
是js事件机制一个重要的应用
。
1.来个例子
我们要实现一个列表,其中有五项,点击任何一项输出相应的列表项文本。
我们大多数小白会这么写,采用循环进行遍历:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
li {
width: 100px;
background: #efe5ad;
font-size: 20px;
margin: 10px;
}
</style>
<body>
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let li = document.getElementsByTagName("li");
for (let i = 0; i < li.length; i++) {
li[i].addEventListener("click", () => {
console.log(li[i].innerHTML)
})
}
</script>
</body>
</html>
但是这样做每次循环都需要创建一个监听事件,这就不优雅了,试着利用js事件流来优化它一下吧!
<script>
//事件代理
let ul = document.getElementById("ul");
ul.addEventListener("click", (event) => {
console.log(event.target.innerHTML);
})
</script>
我们只需要获取ul的dom结构,调用ul监听事件参数event中的taget中的innerHTML就可以得到值啦!
2.优点
- 只需要把事件绑定到
ul
上,占用的内存更小
了 - 可以
动态
给添加的元素绑定监听事件
,不需要每添加一个元素就重新绑定一次。
最后
我们开头的问题想必大家都有答案了,是因为在菜品这个div中默认设置了阻止默认事件的函数stopPropagation(),当点击按钮时只会触发这个按钮的冒泡事件。故点击按钮并不会跳转到这个菜品的详情页。
转载自:https://juejin.cn/post/7232905822279204919