面试题: JavaScript事件流与事件代理

1,666 阅读6分钟

  JavaScript中的事件流是指事件在页面中传播和触发的过程,分为捕获阶段、目标阶段和冒泡阶段。在这个过程中,我们可以利用事件代理和阻止默认行为等技巧来更灵活地处理用户交互,事件代理(或称事件委托)是一种优化事件处理的方法。本文将深入探讨这些概念,帮助读者更好地理解和运用JavaScript中的事件流机制。

什么是JS的事件

  JavaScript事件是指用户与网页交互或浏览器发生特定情况时,可以触发执行特定代码的机制。这些交互包括但不限于用户点击按钮、键盘输入、鼠标移动等。事件是前端开发中实现交互和响应用户操作的基本方式。 以下是一些常见的JavaScript事件:

  1. 鼠标事件:

    • click: 单击鼠标触发。
    • mouseovermouseout: 鼠标移入和移出元素触发。
    • mousedownmouseupmousemove: 鼠标按下、释放和移动触发。
  2. 键盘事件:

    • keydownkeyupkeypress: 键盘按下、释放和按下并立即释放触发。等

事件流触发过程

  事件流分为捕获阶段、目标阶段和冒泡阶段。 案例1:有3个不同颜色的div容器,最外层红色盒子里放着蓝色的盒子,蓝色盒子里放着黄色盒子,盒子之间层层叠加。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #a{
      width: 400px;
      height: 400px;
      background-color: rgb(245, 56, 13);
    }
    #b{
      width: 200px;
      height: 200px;
      background-color: rgb(27, 140, 238);
    }
    #c{
      width: 100px;
      height: 100px;
      background: #000;
    }
  </style>
</head>
<body>
  <div id="a">
    <div id="b">
      <div id="c"></div>
    </div>
  </div>
  <script>
    let a = document.getElementById('a')
    let b = document.getElementById('b')
    let c = document.getElementById('c')
     a.addEventListener('click', () => {
       console.log('a被点击');
    })
     b.addEventListener('click', (event) => {
       console.log('b被点击');
    })
    c.addEventListener('click', (event) => {
      console.log('c被点击');
    }
  </script>
</body>
</html>

_)YMX[EDTSIW7]BW}RN`QG8.png 当我点击黄色方块时 打印的顺序结果为 c b a

image.png   当我点击蓝色方块时 打印的顺序结果为 b a
这个结果为什么和我想得不一样呢 不着急 接下来我跟你讲讲清楚

捕获事件

捕获事件的流程包括事件从window对象向下传播到触发事件的目标元素的过程。以下是捕获事件的详细流程:

  1. 开始阶段: 事件开始于window对象,即全局对象。
  2. 捕获阶段开始: 事件在DOM树中向下传播,沿着父元素到子元素的路径。在这个过程中,如果某个祖先元素(父元素、祖父元素等)上有注册捕获事件的处理程序,这些处理程序将按照从外到内的顺序被触发。
  3. 目标元素: 事件传播到达目标元素,即触发事件的元素。在捕获阶段的最后一步,目标元素上注册的捕获事件处理程序将被触发。

  通俗易懂的就是指的是事件从DOM根节点沿着DOM树往下传递,直到到达目标元素的父元素,依次触发父元素上的事件处理程序。着重于从外向内传播。

上面的案例就是从winow--> html --> body --->div

冒泡阶段: 事件从目标元素开始向上冒泡到window对象。在这个过程中,如果目标元素或其祖先元素上有注册冒泡事件的处理程序,这些处理程序将按照从内到外的顺序被触发。

上面案例就是 div---> body----> html--->window

  指的是事件从目标元素开始往上冒泡,直到到达DOM根节点,依次触发所有父元素上的事件处理程序。着重于从内向外传播。

这个整体流程形成了事件的捕获阶段、目标阶段和冒泡阶段。

捕获事件与冒泡事件的互斥关系

  捕获事件和冒泡事件是互斥的,即同一个事件不能同时在捕获和冒泡阶段触发。

再来个案例修改js代码

 a.addEventListener('click', function
            () {
            console.log('a被点击');
        },true)
  b.addEventListener('click', function
            () {
            console.log('b被点击');
        },true)
  c.addEventListener('click', event => {
            console.log('c被点击');},true)

ZHMP   当我点击黄色方块时 打印的顺序结果为 a b c

  在实际应用中,可以通过addEventListener方法的第三个参数来控制是在捕获阶段还是冒泡阶段处理事件。若该参数为true,则在捕获阶段处理事件;若为false或省略,则在冒泡阶段处理事件。

阻止默认行为

  在处理事件时,有时我们需要阻止事件的默认行为,

  1. event.stopPropagation(): 该方法用于阻止事件继续传播,即停止事件在DOM树上的捕获或冒泡过程。
  2. event.stopImmediatePropagation(): 除了阻止事件传播外,该方法还会阻止元素上绑定的其他事件的执行。

  这两个方法的灵活运用可以有效地控制事件的传播和执行过程。

  第三个案例 修改js代码

 a.addEventListener('click', function
            () {
            console.log('a被点击');


        })
        b.addEventListener('click', function
            () {
            console.log('b被点击');
            event.stopPropagation();
        })
        c.addEventListener('click', event => {
            console.log('c被点击');
        })

AZ}9ZKPN1M0~D77)DAGD3(2.png

  当我们addEventListener方法的第三个参数为false是默认为冒泡传播是触发,则 event.stopPropagation()阻止冒泡转播 所有打印的结果为 c b

还有一种案例修改js代码

 a.addEventListener('click', function
            () {
            console.log('a被点击');


        }, true)
        b.addEventListener('click', function
            () {
            console.log('b被点击');
            event.stopPropagation();
        })
        c.addEventListener('click', event => {
            console.log('c被点击');
        })

8

  当b的addEventListener方法的第三个参数为true是默认为冒泡传播是触发,则 event.stopPropagation()阻止捕获转播 所有打印的结果为 a b

事件代理(事件委托)

  事件代理是一种利用事件冒泡原理的技术,将事件绑定在父元素上,从而通过事件流触发子元素上的事件。这样做的好处在于:

  1. 减少内存占用:只需在父元素上绑定一个事件,而不是在每个子元素上都绑定事件,节省内存空间。
  2. 动态元素处理:对于动态添加的子元素,无需重新绑定事件,依然可以通过事件代理捕获并处理事件。

案例:

 <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
    </ul>

    <!-- <script>
        // 点击哪一个li,就log出哪个内容
        var lis = document.getElementsByTagName('li');
        lis.__proto__.forEach=Array.prototype.forEach;
        Array.from(lis).forEach(function (li) {
            li.addEventListener('click', function () {
                console.log(this.innerText);
            })
        })   
    </script>

  这样写占用了很多内存,还要绑定很多事件,这样就很麻烦,我们可以用事件委托的方法 代码如下

<script>
        //   点击哪一个li,就log出哪个内容
        var ul = document.getElementsByTagName('ul')[0];
        ul.addEventListener('click', (event) => {
            // 此时事件流从哪里来到ul
            console.log(event.target.innerText);
        })
    </script>

59fb3c950b114594a2e9c8f3d9e07e39~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.gif

  这个方法就用到了冒泡事件 当我们点击到li,就会冒泡到ul上 在用ul里面的target的属性来获取到li 让它输出其内容

结语

  深入理解JavaScript中的事件流触发过程以及相关概念,对于构建更灵活、高效的交互体验至关重要。通过合理使用捕获和冒泡阶段、阻止默认行为以及事件代理等技术,我们能够更好地处理用户的操作,提升网页的交互性和性能。希望本文能够帮助读者更深入地掌握这些关键概念。喜欢的来个关注 点赞 这个也是以后写文章的动力所在 谢谢大家能观看我的文章 咱下期再见 拜拜