#

绝了,框架用多了原生基本都不记得了,今天刷到了这个题顺手写篇博客

# 正文

# 事件流的阶段

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

不过方便懒得跳转的同学,我贴心的截了张图 (其实主要是这个函数太常用了感觉没必要说明用法)

file

# 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);

页面是这样的
file

点击一下中间的 box3
file

为什么是这样呢,我们分析一下

  • 首先,事件流处于捕获阶段,从外到内触发 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 传递了
file

如果你要阻止同一个 DOM 的相同事件继续被触发,可以使用 e.stopImmediatePropagation ()

box1.addEventListener('click', function(event){
    console.log('box1 捕获阶段1');
    event.stopImmediatePropagation();
},true);

file

不过喜欢搞事情的 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;
}

# 后记

困了,睡了,没有后记