1、什么是DOM事件流
事件流描述的是从页面中接收事件的顺序
事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流。
DOM事件流中有三个阶段:捕获阶段,当前目标阶段,冒泡阶段
2、DOM事件流中的三个阶段
JS代码中只能执行捕获或者冒泡其中的一个阶段。
onclick和 attachEvent (ie)只能得到冒泡阶段。
addEventListener(type,listener[,useCapture])第三个参数如果是 true,表示在事件捕获阶段调用事件处理程序;如果是false (不写默认就是false ),表示在事件冒泡阶段调用事件处理程序。
实际开发中我们很少使用事件捕获,我们更关注事件冒泡。
有些事件是没有冒泡的,比如onblur、onfocus、onmouseenter、onmouseleave
2.1、捕获阶段
如果addEventListener第三个参数是true,则处于捕获阶段
捕获过程(自上而下):window \ document \ html \ body \ ...普通html结构 \ father \ son(目标元素)
<body>
<div class="father">
<div class="son">son盒子</div>
</div>
<script>
var son = document.querySelector('.son');
son.addEventListener('click',function(){
alert('son')
},true);
var father = document.querySelector('.father');
father.addEventListener('click',function(){
alert('father')
},true);
</script>
</body>
因为addEventListener的第三个参数为true,所以为捕获阶段。弹出的先后顺序是 father -> son
2.2、冒泡阶段
如果addEventListener第三个参数是false或者省略,则处于冒泡阶段
冒泡阶段(自下而上): son \ father \ body \ html \ document
<div id="outer">
<div id="inner">inner</div>
</div>
<script>
window.onclick=function(){
console.log('window');
}
document.onclick=function(){
console.log('document');
}
document.documentElement.onclick=function(){
console.log('html');
}
document.body.onclick=function(){
console.log('body')
}
const outer = document.getElementById('outer')
const inner = document.getElementById('inner')
outer.onclick=function(){
console.log('out')
}
inner.onclick=function(){
console.log('inner')
}
</script>
1. JS代码只能执行捕获或冒泡其中一个阶段
2. onclick和attachEvent只能得到冒泡阶段
3. 如果addEventListener第三个参数为false或省略,处于冒泡阶段
以上代码点击inner盒子输出顺序: inner、out、body、html、document、window
绑定的事件处理函数是在当前元素事件行为的冒泡阶段(或目标阶段)执行的
3、阻止事件冒泡
3.1、标准写法
利用事件对象里面的stopPropagation()方法
<body>
<div class="father">
<div class="son">son盒子</div>
</div>
<script>
//标准写法∶利用事件对象里面的stopPropagation()方法
//stop 停止Propagation 传播
var son = document.querySelector('.son');
son.addEventListener('click',function(e){
alert('son')
e.stopPropagation();
},false);
var father = document.querySelector('.father');
father.addEventListener('click',function(){
alert('father')
},false);
document.addEventListener('click',function(){
alert('document')
},false);
</script>
</body>
只弹出了son ,没有father
3.2、兼容性写法
var son = document.querySelector('.son');
son.addEventListener('click',function(e){
alert('son')
if (e && e.stopPropagation) {
e.stopPropagation();
}else{
window.event.cancelBubble = true;
}
},false);
4、事件委托(事件代理)
事件委托,也成事件代理,在jQuery中也叫事件委派
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数绑定在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫事件代理(事件委托)
案例:给ul注册点击事件,然后利用事件对象的target来找到当前点击的li,因为点击li,事件会冒泡到ul上,ul有注册事件,就会触发事件监听器
<body>
<ul>
<li>hello world</li>
<li>hello world</li>
<li>hello world</li>
<li>hello world</li>
<li>hello world</li>
</ul>
<script>
//给父节点添加监听器,然后利用冒泡原理影响设置每个子节点。
var ul = document.querySelector('ul');
ul.addEventListener('click',function(e){
alert('hello world');
//e.target()可以得到我们点击的对象
e.target.style.color = 'aqua';
})
</script>
</body>
5、Event对象常用应用
5.1、event.preventDefault()
阻止默认行为
例如:表单点击提交按钮跳转页面,a标签默认页面跳转或锚点定位
如果使用a标签作为普通按钮,实现点击功能,既不跳转页面,也不锚点定位
①、href="javascript:;"
<a href="javascript:;"></a>
②、return false
<a href="http://www.baidu.com">Click</a>
<script>
const a = document.getElementsByTagName("a")[0]
a.onclick=function(e){
return false
}
</script>
③、e.preventDefault()
<a href="http://www.baidu.com">Click</a>
<script>
const a = document.getElementsByTagName("a")[0]
a.onclick=function(e){
e.preventDefault()
}
</script>
5.2、event.stopPropagation()
阻止事件冒泡到父元素,阻止任何父元素处理程序被执行
<div id="outer">
<div id="inner">inner</div>
</div>
<script>
const outer = document.getElementById('outer')
const inner = document.getElementById('inner')
window.onclick=function(){
console.log('window');
}
document.onclick=function(){
console.log('document');
}
document.documentElement.onclick=function(){
console.log('html');
}
document.body.onclick=function(){
console.log('body')
}
outer.onclick=function(){
console.log('out')
}
inner.onclick=function(e){
console.log('inner')
e.stopPropagation()
}
</script>
仅输出inner
5.3、event.stopImmediatePropagation()
既能阻止事件向父元素冒泡,也能阻止元素同类型事件的其他监听被触发。而stopPropagation只能实现前者的效果。
如下代码,id为inner的元素绑定了2个事件处理函数,在第一个事件处理函数中,如果只写了e.stopPropagation(),将阻止事件向父元素冒泡,但事件2的处理函数仍然会执行
但如果换成e.stopImmediatePropagation(),则不仅阻止事件向父元素冒泡,事件2的处理函数也不执行。
<div id="outer">
<div id="inner">inner</div>
</div>
<script>
window.onclick=function(){
console.log('window');
}
document.onclick=function(){
console.log('document');
}
document.documentElement.onclick=function(){
console.log('html');
}
document.body.onclick=function(){
console.log('body')
}
const outer = document.getElementById('outer')
const inner = document.getElementById('inner')
outer.onclick=function(){
console.log('out')
}
inner.addEventListener('click',function(e){
console.log('inner -- 事件1');
// e.stopPropagation()
e.stopImmediatePropagation()
})
inner.addEventListener('click',function(e){
console.log('inner -- 事件2');
})
</script>
5.4、event.currentTarget & event.target
event.target是事件触发的元素,event.currentTarget是当事件沿着DOM触发时事件的当前目标,它总是指向事件绑定的元素
<div id="id1">
<div id="id2">
<div id="id3">
<div id="id4"></div>
</div>
</div>
</div>
<script>
const box1 = document.getElementById('id1')
const box2 = document.getElementById('id2')
const box3 = document.getElementById('id3')
const box4 = document.getElementById('id4')
box1.addEventListener('click',function(e){
console.log(`e.target:${e.target.id},e.currentTarget:${e.currentTarget.id}`)
})
box2.addEventListener('click',function(e){
console.log(`e.target:${e.target.id},e.currentTarget:${e.currentTarget.id}`)
})
box3.addEventListener('click',function(e){
console.log(`e.target:${e.target.id},e.currentTarget:${e.currentTarget.id}`)
})
box4.addEventListener('click',function(e){
console.log(`e.target:${e.target.id},e.currentTarget:${e.currentTarget.id}`)
})
</script>
控制台依次输出:
e.target: id4, e.currentTarget:id4
e.target: id4, e.currentTarget:id3
e.target: id4, e.currentTarget:id2
e.target: id4, e.currentTarget:id1
6、面试题-DOM事件有哪些级别
DOM一共分为4个级别: DOM0级,DOM1级,DOM2级,DOM3级
DOM事件一共分为3个级别: DOM0级事件处理,DOM2级事件处理,DOM3级事件处理
(由于DOM1级没有规定事件相关内容,所以没有DOM1级事件)
DOM0级事件
el.onclick = function(){}
如果给同一个元素、标签绑定多个同类型事件,只有最后一次会被执行
<button id="btn">Click Me!</button>
<script>
const btn = document.getElementById('btn')
btn.onclick = function(){
alert("第一个事件处理函数")
}
btn.onclick = function(){
alert("第二个事件处理函数")
}
btn.onclick = function(){
alert("第三个事件处理函数")
}
</script>
DOM2级事件
el.addEventListener(event-name,callback,useCapture)
event-name:事件名称,可以是标准DOM事件
callback:回调函数,当事件触发时,函数会被注入一个参数(event)
useCapture:默认是false,代表事件处于冒泡阶段, true则为捕获阶段
通过el.addEventListener添加的事件只能通过el.removeEventListener来移除,传入的参数与addEventListener相同。
el.addEventListener添加的事件时,如果传入的是匿名函数,那么将无法移除事件监听。因为两个匿名函数的内存地址是不相同的,故推荐传入具名函数。
通过el.addEventListener可以为同一元素添加多个事件监听。
DOM3级事件
在DOM2级事件的基础上添加了更多的事件类型。
UI事件:当用户与页面上的元素交互时触发,如load,scroll
焦点事件:当元素获得或失去焦点时触发,如focus,blur
鼠标事件:当用户通过鼠标在页面执行操作时触发,如:click,mousedown/mouseup等
滚轮事件:当使用鼠标滚轮时触发,如mousewheel
键盘事件:当用户通过键盘触发页面操作时,如keyup,keydown
合成事件:当为输入法编辑器输入字符时触发,compositionstart
变动事件:当底层DOM结构发生变化时触发,如DOMsubtreeModified
DOM3级事件允许使用者自定义一些事件