[TOC]

事件流指的是事件完整执行过程中的流动路径
假设页面里有个div,当触发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段
简单来说:捕获阶段是从父到子依次触发事件冒泡阶段是从子到父元素依次触发事件
JS事件流 - 图1
演示代码:

<style>
    .outer {
      width: 200px;
      height: 200px;
      background-color: cadetblue;
    }

    .inner {
      width: 100px;
      height: 100px;
      background-color: chartreuse;
    }
</style>

<body>
  <div class="outer">
    <div class="inner"></div>
  </div>

  <script>
    let outer = document.querySelector('.outer')
    let inner = document.querySelector('.inner')

    document.addEventListener('click', function () {
      console.log('document')
    })

    outer.addEventListener('click', function () {
      console.log('外层div')
    })

    inner.addEventListener('click', function () {
      console.log('内层div~~~~~~')
    })
  </script>
</body>

事件冒泡

当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡
简单理解: 当一个元素触发事件后,会依次向上调用所有父级元素的同名事件
事件冒泡是默认存在的

代码参见上面的示例,因为默认 事件就是在冒泡阶段触发的

事件捕获

从DOM的根元素开始去执行对应的事件 (从外到里)
事件捕获需要写对应代码才能看到效果

元素.addEventListener(事件类型, 处理函数, 是否使用捕获机制)
// 元素.addEventListener('事件名称', '处理函数', false) // 默认第三个参数就是false,表示事件在冒泡阶段触发
// 元素.addEventListener('事件名称', '处理函数', true) // 如果第三个参数为true,表示事件在捕获阶段触发

document.addEventListener('click', function () {
  console.log('document')
}, true)

outer.addEventListener('click', function () {
  console.log('外层div')
}, true)

inner.addEventListener('click', function () {
  console.log('内层div~~~~~~')
}, true)

小结

  • DOM 2级事件,可以通过传入第三个参数true,来将函数绑定在捕获阶段或者冒泡阶段执行。
  • DOM 2级事件若传入false代表冒泡阶段触发,默认就是false
  • 使用 on 的事件都是 DOM 0 级事件,事件绑定函数的执行,都是在冒泡阶段,没有捕获阶段,只要绑定了事件函数,就会按照冒泡顺序执行,单击事件会比双击事件先触发执行。

    阻止事件流动

    因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素
    若想把事件就限制在当前元素内,就需要阻止事件流动
    阻止事件流动需要拿到事件对象
    语法: e.stopPropagation()
    此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效

    <body>
    <div class="outer">
      <div class="inner"></div>
    </div>
    
    <script>
      let outer = document.querySelector('.outer')
      let inner = document.querySelector('.inner')
    
      // 元素.addEventListener('事件名称', '处理函数', false) // 默认第三个参数就是false,表示事件在冒泡阶段触发
      // 元素.addEventListener('事件名称', '处理函数', true) // 如果第三个参数为true,表示事件在捕获阶段触发
    
      document.addEventListener('click', function (e) {
        console.log('document')
        e.stopPropagation()
      }, true)
    
      outer.addEventListener('click', function () {
        console.log('外层div')
      }, true)
    
      inner.addEventListener('click', function (e) {
        console.log('内层div~~~~~~')
        // 当触发了这个事件后,阻止事件继续流动
        e.stopPropagation() // 注意加小括号
      }, true)
    </script>
    </body>
    

    为什么人们总喜欢说阻止事件冒泡,而很少说阻止事件的流动? 原因是默认事件是在冒泡阶段触发的,二是因为很少让事件在捕获阶段触发。

对比两组鼠标事件

有两组和事件冒泡相关的鼠标事件如下:
鼠标移入和离开事件:

  • mouseover 和 mouseout 会有冒泡效果
  • mouseenter 和 mouseleave 没有冒泡效果(推荐) ```javascript

<a name="yt4cR"></a>
## 阻止标签的默认行为
标签也有默认行为,比如 `<a href="">跳转</a>` ,点击之后,默认就会跳转到指定的页面,这个跳转就是 a 标签的默认行为。<br />还有 form ,当我们点击提交按钮的时候,默认就会将表单数据提交到指定的页面。<br />很多场景,我们并不希望这些默认行为发生,则可以使用 `e.preventDefault()` 来阻止标签的默认行为。
```javascript
<a id="tobaidu" href="https://www.baidu.com">百度一下</a>
<hr>
<!-- href="" 超链接也会跳转,只不过跳转到了当前页面 -->
<!-- <a href="javascript:这里可以写一段js代码">你才我跳转到了哪里</a> -->
<!-- <a href="javascript:">你才我跳转到了哪里</a> -->
<a href="javascript:;">你才我跳转到了哪里</a>

<hr>
<hr>

<!-- form也有默认行为,就是提交数据这种行为 -->
<!-- 只要点击提交按钮,就会提交数据 -->
<!-- 默认会将我们填写的表单数据提交到当前页面 -->
<!-- 如果希望将数据提交到其他页面,可以设置form的action属性 -->
<form action="https://www.baidu.com">
  <input type="text" name="username"><br />
  <input type="password" name="pwd"><br />
  <button>登录</button>
</form>


<script>
  let btn = document.querySelector('button')
  btn.addEventListener('click', function (e) {
    e.preventDefault() // 阻止表单的默认提交行为
    console.log(123123123)
  })

  // a 标签,也可以这样阻止默认行为。
  // a 标签还有其他阻止默认行为的方案
  document.querySelector('#tobaidu').addEventListener('click', function (e) {
    e.preventDefault()
  })
</script>

DOM 0级和DOM 2级注册事件的区别

  • 传统on注册(L0)
    • 语法: 元素.onclick = function () {}
    • 同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
    • 直接使用null覆盖就可以实现事件的解绑
    • 都是冒泡阶段执行的
  • 事件监听注册(L2)

    • 语法: 元素.addEventListener(事件类型, 事件处理函数, 是否使用捕获)
    • 后面注册的事件不会覆盖前面注册的事件(同一个事件)
    • 可以通过第三个参数去确定是在冒泡或者捕获阶段执行
    • 必须使用 元素.removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段),匿名函数无法被解绑,所以要移除事件的话,不能使用匿名函数 ```javascript

    ```