序
绝了,框架用多了原生基本都不记得了,今天刷到了这个题顺手写篇博客
正文
事件流的阶段
DOM事件流分为三个阶段:捕获阶段
、处于目标阶段
、冒泡阶段
。DOM事件流会先调用捕获阶段
的处理函数,然后调用目标阶段
的处理函数,最后调用冒泡阶段
的处理函数。
在这里,1-3是捕获阶段,4-5是处于目标阶段,6-8是冒泡阶段
绑定事件的方法
HTML内联绑定
1
| <div id="box1" onclick="console.log('box1 内联绑定')"></div>
|
DOM属性绑定
1 2 3 4
| let box1 = document.querySelector("#box1"); box1.onclick = function () { console.log('box1 DOM属性绑定'); }
|
通过addEventListener绑定
为什么不看看万能的MDN呢
不过方便懒得跳转的同学,我贴心的截了张图(其实主要是这个函数太常用了感觉没必要说明用法)
tips
addEventListener可以多次给一个元素绑定多个监听同一个事件的回调,而DOM属性绑定和HTML内联绑定都只能绑定一个事件,之后绑定的事件会覆盖之前绑定的事件,而且两者的事件也会相互覆盖。
HTML内联绑定和DOM属性绑定的事件都是绑定到冒泡阶段,addEventListener绑定的事件可以选择绑定到冒泡阶段和捕获阶段
事件的触发顺序
1 2 3 4 5
| <div id="box1" onclick="console.log('box1 内联绑定')"> <div id="box2"> <div id="box3"></div> </div> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| 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按下面的顺序绑定了回调
1 2 3 4 5 6 7 8 9 10 11 12
| 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按下面的顺序绑定了回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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绑定的冒泡事件顺序
1 2 3 4 5 6 7 8 9
| 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绑定的冒泡事件顺序
1 2 3 4 5 6 7 8 9
| 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的捕获事件
1 2 3 4
| box1.addEventListener('click', function(event){ console.log('box1 捕获阶段1'); event.stopPropagation(); },true);
|
点击box3,事件不会继续向其他DOM传递了
如果你要阻止同一个DOM的相同事件继续被触发,可以使用e.stopImmediatePropagation()
1 2 3 4
| box1.addEventListener('click', function(event){ console.log('box1 捕获阶段1'); event.stopImmediatePropagation(); },true);
|
不过喜欢搞事情的IE总是能搞特殊,它使用e.cancelBubble = true这样的方式来阻止事件冒泡(旧版IE不支持事件捕获)
所以完整代码应该是这样的
1 2 3 4 5 6 7 8
| function stopPropagation(event) { event = event || window.event; if (event && event.stopPropagation){ event.stopPropagation(); }else{ event.cancelBubble = true; } }
|
阻止默认事件
如果你要阻止一些默认事件,比如href的链接跳转,submit的表单提交等,可以使用event.preventDefault()或者在绑定的函数里return false来搞定
1 2 3 4 5 6 7
| a.onclick = function(){ return false; };
a.onclick = function(event){ return event.preventDefault(); };
|
还是我们的IE喜欢搞特殊,IE使用e.returnValue = false来取消默认事件,所以完整的代码是这样的
1 2 3 4 5 6 7 8 9
| function stopDefault(event) { event = event || window.event; if (event && event.preventDefault){ event.preventDefault(); }else{ event.returnValue = false; } return false; }
|
后记
困了,睡了,没有后记