• 事件的冒泡和捕获


    DOM事件发生后,会在当前节点和父节点之间传播(propagation)。

    事件传播按照传播顺序分为三个阶段。对应Event.prototype.eventPhase的三个状态:

      const phases = {
        1: 'capture', //捕获
        2: 'target',   // 目标
        3: 'bubble'   // 冒泡
      }

    一. 事件传播阶段

    1. 捕获阶段

    事件按照window->document(window.document)->html(window.documentElement)

    ->body(document.body)->父节点->当前节点(target)顺序传递

    对应监听函数如下:

    element.addEventListener = function(type, function(e) {
       // TODO
    }, true);  

    2. 目标阶段

    当触发对应的节点就是当前监听的节点。

    不管addEventListener的第三个参数是true还是false,都会触发执行。

    3. 冒泡阶段

    事件按照target->父节点-> body->html->document->window顺序传递.

    html标签的on[event] 属性和dom对象的on[event]方法都是监听的冒泡阶段的事件。

    示例:

    <body>
      <div id="container">
        <div id="root">
          <button id="btn">ClickMe</button>
        </div>    
      </div>
    
    <script>  
      const phases = {
        1: 'capture',
        2: 'target',
        3: 'bubble'
      }
      container.addEventListener('click', function(e) {
        console.log('container->',phases[e.eventPhase]);
      },true);
      container.addEventListener('click', function(e) {
        console.log('container->',phases[e.eventPhase]);
      },false);
      root.addEventListener('click', function(e) {
        console.log('root->',phases[e.eventPhase]);
      },true);
      root.addEventListener('click', function(e) {
        console.log('root->',phases[e.eventPhase]);
      },false);
      btn.addEventListener('click', function(e) {
        console.log('btn->',phases[e.eventPhase]);
      }, true)
      btn.addEventListener('click', function(e) {
        console.log('btn->',phases[e.eventPhase]);
      }, false)
    </script> 

    运行结果:

    // 当单击button时
    container->capture
    root->capture
    btn->target
    btn->target
    root->bubble
    container->capture
    
    // 当单击root时
    container->capture
    root->target
    root->target
    container->bubble
    
    // 当单击container时
    container->target
    container->target

    二. 事件传播的相关方法

    1. event.stopPropagation()

    停止向上/向下传播;但是还可以监听同一事件。

    // 事件传播到 element 元素后,就不再向下传播了
    element.addEventListener('click', function (event) {
      event.stopPropagation();
    }, true);
    
    // 事件冒泡到 element 元素后,就不再向上冒泡了
    element.addEventListener('click', function (event) {
      event.stopPropagation();
    }, false);

    示例:

      <div id="container">
        <div id="root">
          <button id="btn">ClickMe</button>
        </div>    
      </div>
    <script>  
      const phases = {
        1: 'capture',
        2: 'target',
        3: 'bubble'
      }
      container.addEventListener('click', function(e) {
        console.log('container->',phases[e.eventPhase]);
      },true);
      container.addEventListener('click', function(e) {
        console.log('container->',phases[e.eventPhase]);
      },false);
      root.addEventListener('click', function(e) {
        event.stopPropagation(); // 停止传播
        console.log('root->',phases[e.eventPhase]);
      },true);
      root.addEventListener('click', function(e) {
        console.log('root->',phases[e.eventPhase]);
      },false);
      btn.addEventListener('click', function(e) {
        console.log('btn->',phases[e.eventPhase]);
      }, true)
      btn.addEventListener('click', function(e) {
        console.log('btn->',phases[e.eventPhase]);
      }, false)
    </script> 
    View Code

    运行结果如下:

    // 单击btn
    container->capture
    root->capture
    
    // 单击root
    container->capture
    root->target
    root->target  // 不会拦截目标节点的事件多次触发

    2. event.stopImmediatePropagation()

    停止事件传播,并且停止事件再次监听同样的事件

    element.addEventListener('click', function (event) {
      event.stopImmediatePropagation();
      console.log(1);
    });
    
    element.addEventListener('click', function(event) {
      // 不会被触发
      console.log(2);
    });

    示例

    <body>
      <div id="container">
        <div id="root">
          <button id="btn">ClickMe</button>
        </div>    
      </div>
    <script>  
      const phases = {
        1: 'capture',
        2: 'target',
        3: 'bubble'
      }
      container.addEventListener('click', function(e) {
        console.log('container->',phases[e.eventPhase]);
      },true);
      container.addEventListener('click', function(e) {
        console.log('container->',phases[e.eventPhase]);
      },false);
      root.addEventListener('click', function(e) {
        event.stopImmediatePropagation(); // 立即停止传播
        console.log('root->',phases[e.eventPhase]);
      },true);
      root.addEventListener('click', function(e) {
        console.log('root->',phases[e.eventPhase]);
      },false);
      btn.addEventListener('click', function(e) {
        console.log('btn->',phases[e.eventPhase]);
      }, true)
      btn.addEventListener('click', function(e) {
        console.log('btn->',phases[e.eventPhase]);
      }, false)
    </script> 
    View Code

    运行结果如下:

    // 单击btn
    container->capture
    root->capture
    
    //单击root
    container->capture
    root->target //目标节点只能触发一次事件

    3. event.preventDefault()

    取消浏览器对当前事件的默认行为。该方法有效前提是事件的cancelable属性为true。

    所有浏览器子有的一些事件(click,mouseover等)该属性都是true;

    自定义的事件默认cancelable属性为false,需要手动设置为true。

    const event = new Event('myevent', {cancelable: true})

    事件的默认行为常见的有:

    1)向input键入内容,会在文本框中显示输入的数据;

        通过keypress事件监听可以阻止文本输入,使无法输入。

    2)单击单选框/复选框,会出现选中效果;

        通过click事件监听可以取消选中行为,使无法选中。

    3)单击<a>标签的链接,会出现跳转;

        通过click事件监听可以取消跳转行为,使无法跳转。

    4)空格键使页码向下滚动;

    示例:

    <!--
     * @Author: LyraLee
     * @Date: 2019-10-30 08:53:28
     * @LastEditTime: 2019-11-11 18:48:19
     * @Description: preventDefault()
     -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>串行异步操作</title>
      <style>
        body{
          height: 2000px;
        }
      </style>
    </head>
    <body>
      <input id="checkboxElement" type="checkbox"/>
      <a id="toBaidu" href="http://www.baidu.com">跳转到百度</a>
      <input id="inputElement" type="text"/>
    <script>  
      checkboxElement.addEventListener('click', function(e) {
        e.preventDefault(); //无法选中
      })  
      toBaidu.addEventListener('click', function(e) {
        e.preventDefault(); // 无法跳转
      })
      inputElement.addEventListener('keypress', function(e) {
        console.log(e);
        if (e.charCode < 97 || e.charCode > 122) { 
          e.preventDefault();//键入的字符只能是a-z
          alert('only lowercase letters');
        }
      })
      document.body.addEventListener('keypress', function(e) {
        if (e.charCode === 32) {// 是空格键
          e.preventDefault(); //阻止空格后页码向下滑动
        }
      })
    </script> 
    </body>
    </html>
    View Code

    4. event.composedPath()

    返回一个数组,成员是目标节点到window节点的冒泡的所有路径。

    示例:

    <body>
      <div id="container">
        <div id="second">
          <div id='bottom'>
            ClickMe
          </div>
        </div>
      </div>
    <script>  
      bottom.addEventListener('click', function(e) {
        console.log(e.composedPath()); 
        //[div#bottom, div#second, div#container, body, html, document, Window]
      },false)
    </script> 

    三. 事件传播相关属性

    1. 只读属性

    1. event.eventBubbles

    返回值:布尔值;表示是否可以冒泡

    浏览器原生事件默认都是true,可以冒泡;

    通过Event构造函数,自定义的事件默认为false,不能冒泡,需要手动设置。

    const event = new Event('lyra', {bubbles: true})

    2. event.eventPhase

    返回值:0-3的数字;表示当前当前事件传播所处的阶段。

    0: 事件未发生
    1: 事件捕获阶段
    2: 目标阶段
    3: 事件冒泡阶段

    3. event.cancelable

    返回值:布尔值;表示是否可以取消默认行为。

    浏览器原生事件默认都是true,可以取消,可以调用event.preventDefault()方法;

    通过Event构造函数,自定义的事件默认false, 需要手动设置后才能调用event.preventDefault();

    const event = new Event('lyra', { cancelable: true})

    应用:

    可以用来作为使用preventDefault()方法的前置条件

      <input id="enter" type="text" />
      <script>
        enter.addEventListener('keypress', function(e) {
          if(e.cancelable) {
            e.preventDefault();
          }else {
    console.warn('can not be cancelled')
    } })
    </script>

    4. event.defaultPrevented

    返回值: 布尔值;表示是否已经调用过preventDefault()方法

    5.event.currentTarget

    返回值:监听事件绑定的元素;相当于this; 不会变化

    6.event.target

    返回值:事件当前作用的元素;随触发节点实时变化;

    7.event.type

    返回值:事件类型

    8.event.timeStamp

    返回值: 毫秒时间,从网页加载完成到事件触发的时间;表示时间戳

    应用:

    可以通过两次mousemove触发的时间戳的差值,来计算鼠标移动速度。

    var previousX;
    var previousY;
    var previousT;
    
    window.addEventListener('mousemove', function(event) {
      if (
        previousX !== undefined &&
        previousY !== undefined &&
        previousT !== undefined
      ) {
        var deltaX = event.screenX - previousX;
        var deltaY = event.screenY - previousY;
        var deltaD = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
    
        var deltaT = event.timeStamp - previousT;
        console.log(deltaD / deltaT * 1000);
      }
    
      previousX = event.screenX;
      previousY = event.screenY;
      previousT = event.timeStamp;
    });
    View Code

    9.event.isTrusted

    返回值:布尔值;表示是否是用户行为触发的,而不是用dispatch方法触发的。

    一般原生事件返回true;自定义事件返回false。

    10. event.detail

    返回值: 事件相关信息;自定义事件中,返回用户自定义的数据

    如:click事件的点击次数;滚轮的滚动距离

    container.addEventListener('click', function(e) {
      console.log('container->',e.detail);
    },true);
    // 如果单击,返回1
    // 如果双击,返回2
    // 如果N次连续点击,返回N

    2. 可写属性

    1. event.cancelBubble

    如果设为true, 可以阻止事件传播;不止阻止冒泡,也会阻止捕获;

    event.cancelBubble = true; 
    // 相当于
    event.stopPropagation();

    四.应用

    1.多个字节点需要添加监听事件时

      <!--实现鼠标悬浮后,背景色修改为li便签中的颜色-->
      <ul id="container">
        <li>red</li>
        <li>yellow</li>
        <li>purple</li>
        <li>pink</li>
      </ul>
      <script>  
        // 该功能应该将监听事件添加到父节点上,否则需要添加四个监听事件
        container.addEventListener('mouseover', function(e) {
          if (e.target.tagName.toLowerCase() === 'li') {
            e.target.style.backgroundColor = e.target.innerHTML;
          } 
        })
        container.addEventListener('mouseout', function(e) { // 不能用mouseleave
          if (e.target.tagName.toLowerCase() === 'li') {
            console.log('innner');
            e.target.style.backgroundColor = '#fff';
          } 
        })
      </script> 

    2. 父子节点同时添加监听事件

    实际工作中,对于有复杂操作的地方,经常出现父子字节同时添加同一监听事件,但是实现不同功能的情况。

    示例:

    要求单击某菜单选中该菜单,将其id赋值给一个变量;菜单右侧有个提示图标,单击弹出提示框。

    要求单击提示图标的时候,不选中该菜单。

    分析:

    要求子节点的事件不触发父节点的监听函数;使用stopPropagation();

      <ul id="container">
        <li id="menu1"><span>菜单1...</span><span class="arrow">▶️</span></li>
      </ul>
      <script>  
        let checked;
        menu1.addEventListener('click', function(e) {
          console.log(this.id);
          checked = this.id;
        })
        const arrow = document.querySelector('.arrow');
        arrow.onclick = function(e) { // 相当于addEventListen('click',fn,false)
          e.stopPropagation(); //阻止冒泡,触发父节点监听事件
          console.log('--arror---')
        }
      </script> 
  • 相关阅读:
    柯南君:看大数据时代下的IT架构(3)消息队列之RabbitMQ-安装、配置与监控
    柯南君:看大数据时代下的IT架构(2)消息队列之RabbitMQ-基础概念详细介绍
    看大数据时代下的IT架构(1)业界消息队列对比
    vagrant打造自己的开发环境
    !!运维博客
    git搭建服务器
    Tuxedo入门学习
    ubuntu环境ceph配置入门(一)
    谷歌技术&quot;三宝&quot;之MapReduce
    Eclipse中SVN的安装步骤(两种)和用法
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11839619.html
Copyright © 2020-2023  润新知