事件冒泡、事件捕获和事件委托

144 阅读2分钟

事件冒泡、事件捕获和事件委托

事件流

概念:事件流指从页面中接收事件的顺序,有冒泡流和捕获流。

DOM二级事件规定事件流包括三个阶段:

  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段

从网上找到一张图片:

1643945056391.png

事件冒泡和事件捕获

addEventListener最后一个参数,为true则代表使用事件捕获模式 ,false则表示使用事件冒泡模式。默认为false。

冒泡模式

<body>
  <div id="parent">
    <button id="child">click me</button>
  </div>
  <script>
    const parent = document.getElementById('parent')
    const child = document.getElementById('child')

    child.addEventListener('click', (e) => {
      console.log('1 click child');
    })

    parent.addEventListener('click', (e) => {
      console.log('2 click parent');
    })

    document.body.addEventListener('click', (e) => {
      console.log('3 click body');
    })

    document.addEventListener('click', (e) => {
      console.log('4 click document');
    })

    window.addEventListener('click', (e) => {
      console.log('5 click window');
    })
  </script>
</body>

1643945710615.png

捕获模式

<body>
  <div id="parent">
    <button id="child">click me</button>
  </div>
  <script>
    const parent = document.getElementById('parent')
    const child = document.getElementById('child')

    child.addEventListener('click', (e) => {
      console.log('1 click child');
    }, true)

    parent.addEventListener('click', (e) => {
      console.log('2 click parent');
    }, true)

    document.body.addEventListener('click', (e) => {
      console.log('3 click body');
    }, true)

    document.addEventListener('click', (e) => {
      console.log('4 click document');
    }, true)

    window.addEventListener('click', (e) => {
      console.log('5 click window');
    }, true)
  </script>
</body>

1643945846991.png

阻止事件冒泡

stopPropagation()

在上面例子中针对第一个button按钮加上e.stopPropagation()进行阻止冒泡。

child.addEventListener('click', (e) => {
  console.log('1 click child');
  e.stopPropagation()
})

1643946083876.png

事件委托

每当将事件处理程序制定给元素时,运行中的浏览器代码与支持页面交互的JS代码之间就会建立一个连接,而这种连接越多,页面执行起来就越慢。

因为冒泡机制,比如既然点击子元素,也会触发父元素的点击事件,那我们完全可以将子元素的事件要做的事写到父元素的事件里,也就是将子元素的事件处理程序写到父元素的事件处理程序中,这就是事件委托;利用事件委托,只指定一个事件处理程序,就可以管理某一个类型的所有事件;

无限下拉的图片列表,如何监听每个图片的点击

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    img {
      width: 300px;
    }
  </style>
</head>

<body>
  <div id="app">
    <div class="img">
      <a href="#">
        <img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
      </a>
    </div>
    <div class="img">
      <a href="#">
        <img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
      </a>
    </div>
    <div class="img">
      <a href="#">
        <img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
      </a>
    </div>
    <button>加载更多</button>
  </div>
</body>
<script>
  const app = document.getElementById('app')
  app.addEventListener('click', (e) => {
    if (e.target.matches('img')) {
      console.log(e.target);
    }
  })
</script>

</html>

思考一下,这样写就是我们需要每次写代理和普通绑定不一样的写法,我们可不可以封装一个通用的绑定函数来实现,函数的回调里面this指向我们指定的目标元素

面试题:通用事件绑定(bindEvent)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    img {
      width: 300px;
    }
  </style>
</head>

<body>
  <div id="app">
    <div class="img">
      <a href="#">
        <img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
      </a>
    </div>
    <div class="img">
      <a href="#">
        <img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
      </a>
    </div>
    <div class="img">
      <a href="#">
        <img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
      </a>
    </div>
    <button id="btn">加载更多</button>
  </div>
</body>
<script>
  function bindEvent(elem, type, selector, fn) {
    if (fn == null) {
      fn = selector
      selector = null
    }
    elem.addEventListener(type, event => {
      const target = event.target
      if (selector) {
        // 代理绑定 matches正则匹配
        if (target.matches(selector)) {
          fn.call(target, event)
        }
      } else {
        // 普通绑定
        fn.call(target, event)
      }
    })
  }

  const btn = document.getElementById('btn')
  bindEvent(btn, 'click', function (event) {
    console.log(this.innerHTML);
  })

  const app = document.getElementById('app')
  // 这里注意回调函数需要使用ES5写法,下面的this才指向目标元素
  bindEvent(app, 'click', 'img', function (event) {
    console.log(this.src);
  })
</script>

</html>