事件是用户或浏览器自己执行的某种动作,是文档或者浏览器发生的一些交互瞬间,比如点击(click)按钮等。JS与html之间的交互式通过事件实现的 DOM 支持大量事件
<html>
<body>
<div id="div1">
<div id="div2">
<button></button>
</div>
</div>
</body>
</html>
当点击按钮button,页面上哪些元素会出发这个事件,上面的div2,div1等是否也会出发这个点击事件。
这就牵扯到事件流,从上面的思考,我们知道它描述的是事件触发顺序,那上文中是按钮和其容器元素都触发吗,它们谁先触发呢?这可不是确定的,得看是哪种类型的事件流了。
事件捕获
看上面的代码
首先按照 Window-->Document-->html-->body-->div1--->div2-->button顺序看有没有函数监听。即从外向内找监听函数,叫事件捕获
事件冒泡
然后按照 botton-->div2-->div1-->body-->html-->Document-->Window顺序看有没有函数监听。即从内向外找监听函数,叫事件冒泡
如图所示
DOM事件流
事件流有三个阶段:事件捕获阶段,目标阶段,事件冒泡阶段
事件绑定API
W3C: xxx.baba.addEventListener('click', fn, bool), 如果bool不传或为falsy, 则fn使用事件冒泡, 反之则fn使用事件捕获。
如果bool不传或为falsy就让fn走冒泡,即当浏览器在冒泡阶段发现xxx有监听函数 就会调用fn并提供事件信息
如果bool为true就让fn走捕获 即当浏览器在捕获阶段发现xxx有监听函数 调用fn并提供事件信息
特例
只有一个div被监听(不考虑父子同时被监听)
fn分别在捕获阶段和冒泡阶段监听click事件
用户点击的元素就是开发者监听
div.addEventLisenter('click', f1)
div.addEventLisenter('click',f2,true)
请问,f1先执行还是f2先执行?
调换位置
div.addEventLisenter('click',f2,true)
div.addEventLisenter('click', f1)
谁先执行
错误答案:f2先执行
正确答案:谁先监听谁先执行
取消冒泡
捕获不可以取消,但是冒泡可以
e.stopPropagation() 可以中断冒泡,浏览器不在往上走
不可阻止默认动作
有些事件不能阻止默认动作
Bubbles-->该事件是否冒泡
Cancelable --> 开发者是否可以阻止默认事件
自定义事件
<div id="div1">
<button id="button1">点击出发frank事件</button>
</div>
<script>
button1.addEventListener('click', () => {
const event = new CustomEvent('frank', {
detail: {
name: 'frank',
age: 10
},
bubbles: true,
cancelable: false
})
button1.dispatchEvent(event) //触发事件
})
button1.addEventListener('frank', (e) => {
console.log(e.detail)
})
</script>
事件委托(事件代理)
把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务。事件委托的原理是DOM元素的事件冒泡
优点
- 省监听数(内存)
<div id='div1'>
<button>button1</button>
<button>button2</button>
<button>button3</button>
...
</div>
//有n多个button
如上面代码所示,如果给每个button都绑定一个函数,那对内存的消耗是非常大的,因此较好的解决办法是将button的点击事件绑定到它的父元素div1上,执行事件的时候再去匹配判断的目标元素
- 可以监听动态元素
例子
<div id="div1"></div>
//过一会添加button
setTimeout(()=>{
const button = document.createElement('button')
button.textContent = 'click1'
div1.appendChild(button)
},1000)
//事件委托
div1.addEventListenter('click',(e)=>{
const t = e.target
if(t.tagName.toLowerCase() === 'button'){
console.log('button 被 click')
}
})
//封装一下
on('click','#div1','button',()=>{
console.log('button 被点击了')
})
function on(eventType, element, selector, fn){
if(!(element instanceof Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType,(e)=>{
cons t = e.target
if(t.matches(selector)){
fn(e)
}
})
}
//Element.matches() 一个元素是不是button,li,div