事件流 -- 捕获事件和冒泡事件

334 阅读3分钟

1、事件流

事件流就是指页面接收事件的顺序

即事件发生在元素节点之间按照特定的顺序传播,这个传播过程就是 DOM事件流

事件流分为三个阶段:

  1. 捕获阶段
  2. 当前目标阶段
  3. 冒泡阶段

事件冒泡:IE最早提出,事件开始时由 具体的元素 接收,然后逐级向上传播到DOM最顶层节点的过程

事件捕获:网景最早提出,由DOM最顶层节点开始,然后逐级向下传播到最具体的元素接收的过程

目标阶段:到达的具体元素

当我们给div注册一个点击事件时:

img

1.1 捕获阶段和冒泡阶段

  1. JavaScript只能执行捕获 或 冒泡其中一个阶段
  2. onclickattachEvent 只能得到冒泡阶段
  3. addEventListener(type, listener[, useCapture]) 第三个参数如果是true,表示在事件捕获阶段调用事件处理程序;如果是false(不写默认是false),表示在事件冒泡阶段调用事件处理程序
  4. 有的事件是没有冒泡的,如:onbluronfocusonmouseenteronmouseleave
  5. 实际开发中,我们很少用事件捕获,我们更关注事件冒泡
<div class="parent">
    <div class="son"></div>
</div>
let parent = document.querySelector('.parent')
let son = document.querySelector('.son');

捕获阶段

// 当参数是true,处于捕获阶段,即从上往下, 先 document -> html -> body -> father -> son
// 当点击son时,先弹出father,再弹出 son

son.addEventListener('click', function() {
    alert('son')
}, true)

parent.addEventListener('click', function() {
    alert('father')
}, true)

冒泡阶段

// 当参数是false或不写,处于冒泡阶段,即从上往下, 先son -> father -> body -> html -> document
// 当点击son时,先弹出son,再弹出 father

son.addEventListener('click', function() {
    alert('son')
}, false)

parent.addEventListener('click', function() {
    alert('father')
}, false)

2.2 阻止事件冒泡

阻止事件冒泡:e.stopPropagation()

使用方法很简单,你想要从哪个节点开始阻止事件冒泡,就把该语句放到哪个事件处理程序里面

  • e.stopPropagation(); 添加在 son元素 上面,当点击 son 时,其以上的父元素就都不会冒出来;
  • 如果father上面也有父元素且不想让他们冒出来,同样需要在 father 上添加 e.stopPropagation();
<div class="parent">
    <a href="https://www.baidu.com"></a>
</div>
let parent = document.querySelector('.parent')
let baidu = document.querySelector('a');

// 当我们点击baidu时,直接跳转到百度首页,而不会弹出father
baidu.addEventListener('click', function(e) {
	e.stopPropagation()
})

parent.addEventListener('click', function() {
    alert('father')
})

但是注意了,

  • e.stopPropagation() 有兼容性问题
  • 低版本浏览器使用:window.event.cancelBubble = true ,非标准

需要考虑兼容性问题时,可以做个判断:

if (e && e.stopPropagation()) {
    e.stopPropagation()
} else {
    window.event.cancelBubble = true
}

现在一般直接使用 e.stopPropagation() 就可以了

2.3 事件委托

在多数时候,冒泡事件确实会给我们带来麻烦,我们都会选择阻止事件冒泡,但是,冒泡事件也并不是一无是处

当我们点击下面的li 时,希望给它添加上一个样式,以前我们是用for找到该li,为每一个li都绑定一个点击事件,非常繁琐,而且访问DOM的次数越多,这就会延长整个页面的交互就绪时间

<ul>
    <li>爱真的需要勇气1</li>
    <li>爱真的需要勇气2</li>
    <li>爱真的需要勇气3</li>
    <li>爱真的需要勇气4</li>
    <li>爱真的需要勇气5</li>
</ul>

现在我们可以利用事件委托来简化这个过程

事件委托也称为事件代理,在 jQuery 里面称为事件委派

事件委托原理

不是每一个子节点都单独设置事件监听器,而是把事件监听器设置在其父节点上,然后利用冒泡原理影响每一个子节点

所以,上面例子我们可以给ul注册一个点击事件,然后利用事件对象的target来找到当前点击的 li,事件会冒泡到li的父节点ul上,ul有注册事件,就会触发事件监听器

事件委托的作用:我们只操作了一次DOM,提高了程序的性能

let ul = document.querySelector('ul')
let lis = document.querySelectorAll('li');

ul.addEventListener('click', function(e) {
	// e.target 找到当前点击的对象li
	// 排他思想
    for (let i = 0; i < lis.length; i++) {
        lis[i].style.color = ''
    }
    // 为其添加样式
    e.target.style.color = 'red'
})