DOM 事件模型是浏览器中一个很重要的功能,它可以让用户与浏览器交互起来,同时 DOM 事件也是前端面试的高频题,这个知识点是必须要掌握的
事件捕获和冒泡
w3c 上有一个图很明确的解释了捕获阶段和冒泡阶段的先后顺序
看到这我知道你还是不知道这个事件模型有什么用,直接上例子
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>DOM事件模型</title>
</head>
<style>
*{
padding: 0;
margin: 0;
box-sizing: border-box;
}
div{
border: 1px solid black;
}
.div1{
margin: 100px;
padding: 15px;
width: 90px;
height: 90px;
border-radius: 50%;
background: red;
}
.div2{
padding: 15px;
width: 60px;
height: 60px;
border-radius: 50%;
background: blue;
}
.div3{
width: 30px;
height: 30px;
border-radius: 50%;
background: green;
}
.x{
background: transparent;
}
</style>
<body>
<div class="div1">
<div class="div2">
<div class="div3"></div>
</div>
</div>
</body>
</html>
这样利用三个 div 实现一个飞镖圆盘,接下来为他们添加点击事件
<script>
const div1 = document.querySelector('.div1')
const div2 = document.querySelector('.div2')
const div3 = document.querySelector('.div3')
div1.addEventListener('click',(e)=>{
console.log(1)
})
div2.addEventListener('click',(e)=>{
console.log(2)
})
div3.addEventListener('click',(e)=>{
console.log(3)
})
</script>
打印结果为 3 2 1,可以看到它是从最里面的 div 开始打印,这种顺序叫做冒泡
接下来我们改一下代码
<script>
const div1 = document.querySelector('.div1')
const div2 = document.querySelector('.div2')
const div3 = document.querySelector('.div3')
div1.addEventListener('click',(e)=>{
console.log(1)
},true)
div2.addEventListener('click',(e)=>{
console.log(2)
},true)
div3.addEventListener('click',(e)=>{
console.log(3)
},true)
</script>
打印结果为 1 2 3,可以看到它是从最外面的 div 开始打印,这种顺序叫做捕获, 把 addEventListener 第二个参数改为 true 就可以实现捕获(默认为 false 冒泡)
那么不论是捕获或者冒泡,它总该有个终点吧,要不从哪里开始算捕获,而从哪结束冒泡呢
正确答案是按 window -> document -> html -> body -> 元素, 那我怎么知道的呢,请看上图
Event 对象
在事件模型上有一个很重要的 Event 对象,它可以在触发事件的时候存在事件回调函数里,你就可以依据它做出想要的操作
<script>
const div1 = document.querySelector('.div1')
div1.addEventListener('click',(e)=>{
console.log(e) // div1 的事件对象
console.log(e.target) // 点击的 div 元素,不固定
console.log(e.currentTarget) // 绑定点击事件的元素
})
</script>
具体这个 e 的属性很多,能做出相应的事也很多,请参考 mdn
自定义事件
事件类型有一百多种,不用全看完,会用几个常用的比如 click,focus,change,input,blur 等,其他事件等需要的时候再去 mdn 查
如果这一百多个事件你都不想用,也可以自定义事件
<script>
const div1 = document.querySelector('.div1')
div1.addEventListener('click',()=>{
const myEvent = new CustomEvent('myEvent',{
detail:{
name: 'chauncey'
}
})
div1.dispatchEvent(myEvent)
})
div1.addEventListener('myEvent',(e)=>{
console.log(e.detail.name) // chauncey
})
</script>
这样在点击 div1 的时候就会注册一个自定义事件 myEvent,可以设置一些属性在 myEvent 上,这样在监听这个事件的时候就可以打印出相应的属性
事件委托
什么是事件委托,先设置两个场景
- 假设你的页面有 100 个按钮,现在需要给每个按钮添加一个点击事件,那么就得 addEventListener 100 次,这样代码很啰嗦而且非常浪费内存
- 假设添加事件的元素是 JS 动态生成的,那怎添加事件
这两个场景都不适合直接给元素上添加事件,那么这时候事件委托就来了,它可以很轻松的解决这两个问题
因为事件模型有冒泡机制,在事件触发的时候会一层一层的向上通知,这样触发事件的元素的祖先就会得到通知,我们监听他的祖先就可以实现事件监听
<script>
/**
* delegate 事件委托
* param event 事件类型
* param element 监听元素
* param selector 实际监听元素
* param fn 回调函数
* return element 监听元素
* */
function delegate(event, element, selector, fn) {
element.addEventListener(event, (e) => {
let el = e.target
while (!el.matches(selector)) {
if (el === element) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
})
return element
}
</script>
这样的一个事件委托函数就封装完了,它的原理就是一层一层往上找,如果找不到那就不做操作,如果找到了那就执行回调函数
DOM 事件原型最核心的概念就介绍完了,它还有很多琐碎的知识点也不用专门去了解,知道核心知识就足够应用大部分场景,如果遇到了个别特殊场景再去查 mdn,完