# 序
绝了,框架用多了原生基本都不记得了,今天刷到了这个题顺手写篇博客
# 正文
# 事件流的阶段
DOM 事件流分为三个阶段: 捕获阶段
、 处于目标阶段
、 冒泡阶段
。DOM 事件流会先调用 捕获阶段
的处理函数,然后调用 目标阶段
的处理函数,最后调用 冒泡阶段
的处理函数。
在这里,1-3 是捕获阶段,4-5 是处于目标阶段,6-8 是冒泡阶段
# 绑定事件的方法
# HTML 内联绑定
<div id="box1" onclick="console.log('box1 内联绑定')"></div> |
# DOM 属性绑定
let box1 = document.querySelector("#box1"); | |
box1.onclick = function () { | |
console.log('box1 DOM属性绑定'); | |
} |
# 通过 addEventListener 绑定
为什么不看看万能的 MDN 呢
不过方便懒得跳转的同学,我贴心的截了张图 (其实主要是这个函数太常用了感觉没必要说明用法)
# tips
addEventListener 可以多次给一个元素绑定多个监听同一个事件的回调,而 DOM 属性绑定和 HTML 内联绑定都只能绑定一个事件,之后绑定的事件会覆盖之前绑定的事件,而且两者的事件也会相互覆盖。
HTML 内联绑定和 DOM 属性绑定的事件都是绑定到冒泡阶段,addEventListener 绑定的事件可以选择绑定到冒泡阶段和捕获阶段
# 事件的触发顺序
<div id="box1" onclick="console.log('box1 内联绑定')"> | |
<div id="box2"> | |
<div id="box3"></div> | |
</div> | |
</div> |
let box1 = document.querySelector("#box1"); | |
let box2 = document.querySelector("#box2"); | |
let box3 = document.querySelector("#box3"); | |
box1.addEventListener('click', function(){ | |
console.log('box1 冒泡阶段 这是在onclick前绑定的'); | |
},false); | |
box2.addEventListener('click', function(){ | |
console.log('box2 冒泡阶段 这是在onclick前绑定的'); | |
},false); | |
box2.onclick = function () { | |
console.log('box2 onclick'); | |
} | |
box2.addEventListener('click', function(){ | |
console.log('box2 冒泡阶段 这是在onclick后绑定的'); | |
},false); | |
box3.addEventListener('click', function(){ | |
console.log('box3 冒泡阶段 这是在onclick前绑定的'); | |
},false); | |
box3.onclick = function () { | |
console.log('box3 onclick'); | |
} | |
box3.addEventListener('click', function(){ | |
console.log('box3 冒泡阶段 这是在onclick后绑定的'); | |
},false); | |
box1.addEventListener('click', function(){ | |
console.log('box1 捕获阶段1'); | |
},true); | |
box1.addEventListener('click', function(){ | |
console.log('box1 捕获阶段2'); | |
},true); | |
box2.addEventListener('click', function(){ | |
console.log('box2 捕获阶段1'); | |
},true); | |
box3.addEventListener('click', function(){ | |
console.log('box3 捕获阶段1'); | |
},true); | |
box2.addEventListener('click', function(){ | |
console.log('box2 捕获阶段2'); | |
},true); | |
box3.addEventListener('click', function(){ | |
console.log('box3 捕获阶段2'); | |
},true); | |
box1.onclick = function () { | |
console.log('box1 onclick'); | |
} | |
box1.addEventListener('click', function(){ | |
console.log('box1 冒泡阶段 这是在onclick后绑定的'); | |
},false); |
页面是这样的
点击一下中间的 box3
为什么是这样呢,我们分析一下
- 首先,事件流处于捕获阶段,从外到内触发 dom 绑定的捕获事件,对同一个 DOM,通过 addEventListener 绑定的捕获函数会按绑定的顺序触发。
- 然后,事件进入到目标阶段,这个阶段不区分捕获和冒泡事件,所有事件按照绑定的顺序触发。
- 最后,事件进入到冒泡阶段,这个阶段按照绑定的顺序触发绑定的冒泡事件,各个 DOM 的事件是从内到外触发的
处于捕获阶段时,从外到内触发各个 DOM 绑定的捕获事件,同一个 DOM 的捕获事件按绑定的先后顺序触发,box1,box2 按下面的顺序绑定了回调
box1.addEventListener('click', function(){ | |
console.log('box1 捕获阶段1'); | |
},true); | |
box1.addEventListener('click', function(){ | |
console.log('box1 捕获阶段2'); | |
},true); | |
box2.addEventListener('click', function(){ | |
console.log('box2 捕获阶段1'); | |
},true); | |
box2.addEventListener('click', function(){ | |
console.log('box2 捕获阶段2'); | |
},true); |
所以触发顺序是 box1 捕获阶段 1 -> box1 捕获阶段 2 -> box2 捕获阶段 1 -> box1 捕获阶段 2
处于目标阶段时,不区分事件,全部按照绑定的顺序触发,box3 按下面的顺序绑定了回调
box3.addEventListener('click', function(){ | |
console.log('box3 冒泡阶段 这是在onclick前绑定的'); | |
},false); | |
box3.onclick = function () { | |
console.log('box3 onclick'); | |
} | |
box3.addEventListener('click', function(){ | |
console.log('box3 冒泡阶段 这是在onclick后绑定的'); | |
},false); | |
box3.addEventListener('click', function(){ | |
console.log('box3 捕获阶段1'); | |
},true); | |
box2.addEventListener('click', function(){ | |
console.log('box2 捕获阶段2'); | |
},true); | |
box3.addEventListener('click', function(){ | |
console.log('box3 捕获阶段2'); | |
},true); |
所以触发顺序如下:
box3 冒泡阶段 这是在 onclick 前绑定的 -> box3 onclick -> box3 冒泡阶段 这是在 onclick 后绑定的 -> box3 捕获阶段 1 -> box3 捕获阶段 2
最后是冒泡阶段,冒泡阶段是从内到外的,所以会先触发 box2 的冒泡事件再触发 box1 的冒泡事件,对同一个 DOM 而言,触发顺序安装绑定的顺序触发
box2 绑定的冒泡事件顺序
box2.addEventListener('click', function(){ | |
console.log('box2 冒泡阶段 这是在onclick前绑定的'); | |
},false); | |
box2.onclick = function () { | |
console.log('box2 onclick'); | |
} | |
box2.addEventListener('click', function(){ | |
console.log('box2 冒泡阶段 这是在onclick后绑定的'); | |
},false); |
box1 绑定的冒泡事件顺序
box1.addEventListener('click', function(){ | |
console.log('box1 冒泡阶段 这是在onclick前绑定的'); | |
},false); | |
box1.onclick = function () { | |
console.log('box1 onclick'); | |
} | |
box1.addEventListener('click', function(){ | |
console.log('box1 冒泡阶段 这是在onclick后绑定的'); | |
},false); |
所以触发顺序是这样的
box2 冒泡阶段 这是在 onclick 前绑定的 -> box2 onclick -> box2 冒泡阶段 这是在 onclick 后绑定的 ->
box1 冒泡阶段 这是在 onclick 前绑定的 -> box1 onclick -> box1 冒泡阶段 这是在 onclick 后绑定的
# 其他小知识
# 阻止事件继续传递
使用 e.stopPropagation (),在捕获和冒泡阶段使用都可,之后事件都继续在 DOM 上传递,但是在当前 DOM 上的事件依然会依次执行
略微修改上面的代码,我们修改 box1 的捕获事件
box1.addEventListener('click', function(event){
console.log('box1 捕获阶段1');
event.stopPropagation();
},true);
点击 box3,事件不会继续向其他 DOM 传递了
如果你要阻止同一个 DOM 的相同事件继续被触发,可以使用 e.stopImmediatePropagation ()
box1.addEventListener('click', function(event){
console.log('box1 捕获阶段1');
event.stopImmediatePropagation();
},true);
不过喜欢搞事情的 IE 总是能搞特殊,它使用 e.cancelBubble = true 这样的方式来阻止事件冒泡 (旧版 IE 不支持事件捕获)
所以完整代码应该是这样的
function stopPropagation(event) { | |
event = event || window.event; | |
if (event && event.stopPropagation){ | |
event.stopPropagation(); | |
}else{ | |
event.cancelBubble = true; | |
} | |
} |
# 阻止默认事件
如果你要阻止一些默认事件,比如 href 的链接跳转,submit 的表单提交等,可以使用 event.preventDefault () 或者在绑定的函数里 return false 来搞定
a.onclick = function(){ | |
return false; | |
}; | |
a.onclick = function(event){ | |
return event.preventDefault(); | |
}; |
还是我们的 IE 喜欢搞特殊,IE 使用 e.returnValue = false 来取消默认事件,所以完整的代码是这样的
function stopDefault(event) { | |
event = event || window.event; | |
if (event && event.preventDefault){ | |
event.preventDefault(); | |
}else{ | |
event.returnValue = false; | |
} | |
return false; | |
} |
# 后记
困了,睡了,没有后记