• 562 DOM0级、DOM2级事件,事件对象,阻止默认行为、冒泡,事件传播机制,事件委托


    DOM0级、DOM2级事件及其原理

    事件是元素(或者浏览器)天生自带的行为,只要行为触发,则会触发相关的事件行为;

    事件绑定:基于xxx.onxxx = function(){},属于给某个事件行为绑定方法,再行为触发的时候可以通知方法执行

    * 事件绑定

    * 1.DOM0级事件绑定

    * xxx.onxxx=function(){}

    * 2.DOM2级事件绑定

    * EventTarget.prototype:

    * addEventListener、removeEventListener、dispatchEvent

    * 所有的DOM元素对象(含window)都是EventTarget的实例

    * 非标准浏览器(IE<=8):attachEvent/detachEvent

    * xxx.addEventListener、removeEventListener('xxx',function(){}, false)

    * 【DOM0事件绑定的原理】

    * 每一个DOM元素对象都有很多内置的私有属性,其中包含onxxx这样事件类的私有属性

    * 1、DOM0事件绑定原理

    * 给这些事件类私有属性赋值(当我们触发相关事件行为,浏览器会帮助我们把赋值的函数触发执行)

    * (1)特点1:如果不存在某个事件类型的私有属性,则无法基于这种方式做事件绑定(例如 DOMContentLoaded [等到DOM资源加载完触发的事件])

    * (2)特点2:只能给当前元素的某个事件类型绑定一个方法(私有属性只能赋值一个值)

    *

    * 2、DOM2事件绑定的原理

    * 利用浏览器的事件池机制来完成事件监听和绑定的 【自动去重】

    * (1)特点1:所有事件类型都可以基于这种方式进行事件绑定( 例如 window.addEventListener('DOMContentLoaded',function(){}) )

    * (2)特点2:可以给当前元素的某一个事件类型绑定多个不同的方法

    box.onclick = function () {
        console.log('OK');
    };
    
    
    box.addEventListener('click', function () {
        console.log('DOM2=>OK');
    });
    box.addEventListener('click', function () {
        console.log('DOM2=>NO');
    }); 
    

    1594711379182


    问:window.onload和document.ready区别(JQ中的$(document).ready())

    我之前看过部分JQ源码,其中包含$(document).ready()的处理

    document.addEventListener("DOMContentLoaded", completed)

    =>1)它是基于DOM2级事件中事件池监听实现事件绑定的,所以可以在相同页面中给事件绑定好多不同的方法,也就是可以使用多次

    =>2)它监听的是DOMContentLoaded事件,等待DOM结构一加载完就会触发执行的

    而window.onload本身基于DOM0事件绑定,而且监听的是load事件,所以页面中不仅只能用一次,而且需要等到浏览器所有资源都加载完毕才会触发执行,触发的节点要晚于DOMContentLoaded

    ....

    所以我们之前做项目的时候,有的项目没有用JQ,我想在DOM结构加载完再去做一些事情,我就仿照着JQ写过一些公共方法


    事件对象,阻止默认行为、冒泡

    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
      <title>哈哈</title>
      <style>
        #box {
          box-sizing: border-box;
           100px;
          height: 100px;
          background: lightcoral;
        }
      </style>
    </head>
    
    <body>
      <div id="box"></div>
    
      <script>
    
        /*
        * 事件对象
        *    【鼠标事件对象】 MouseEvent
        *        clientX、clientY: 鼠标触发点距离当前窗口左上角的X/Y轴坐标
        *        pageX、pageY: 鼠标触发点距离BODY(页面首屏)左上角的X/Y轴坐标
        *        path: 存储的是冒泡阶段需要传播的路径(值是捕获阶段获取的)
        *        srcElement、target: 事件源(当前操作的元素)
        *        type: 事件类型
    
        *    【键盘事件对象】 KeyboardEvent
        *        which/keyCode:键盘按键的键盘码
        *        shiftKey/altKy/ctrlKey: 记录是否在按键的时候,按下这些键(组合按键,属性值是布尔类型)
        
        *    【常规事件对象】 Event
        *    【手指事件对象】 TouchEvent
        *    ......
        * 
        * Event.prototype
        *     阻止事件的默认行为
        *     ev.preventDefault() / ev.returnValue=false
        *     
        *     阻止事件的冒泡传播
        *     ev.stopPropagation() / ev.cancelBubble=true
        */
    
    
        let n;
        document.body.onclick = function (ev) {
          console.log(111)
          console.log('BODY');
          console.log(n === ev); // => true
        };
    
        box.onclick = function (ev) {
          // 当事件行为触发,浏览器会把绑定的方法执行,并且把全局下记录当前操作信息的“事件对象”传递给这个函数  =>  当前做了某些操作,不管在哪一个函数中,我们获取的事件对象是同一个,存储的是当前操作的信息,和函数没关系
          console.log('BOX');
          n = ev;
          console.log(ev);
        };
    
    
        document.onkeydown = function (ev) {
          let {
            ctrlKey,
            keyCode
          } = ev;
    
          // 按CTRL+F, 禁止默认行为, 我们期望它刷新页面
          if (ctrlKey && keyCode === 70) {
            ev.preventDefault();
            location.href = location.href;
          }
        };
    
        document.oncontextmenu = function (ev) {
          // 禁止右键菜单
          ev.preventDefault();
          // return false; 这也是阻止默认行为
        }; 
      </script>
    </body>
    
    </html>
    

    事件传播机制

    * 事件具备传播机制

    * 捕获 CAPTURING_PHASE 1

    * 目标 AT_TARGET 2

    * 冒泡 BUBBLING_PHASE 3

    * 当我们触发当前元素的某个事件行为的时候:

    * 1.首先会从最外层window开始,一层层的按照结构向里层查找【捕获:为冒泡阶段提供传播的路径 => ev.path】

    * 2.找到当前的事件源,触发当前事件源的相关行为 【目标】

    * 3.不仅当前事件源的相关行为被触发,其所有祖先元素的相关事件行为都会被触发(在这个过程中,哪一个元素的事件行为绑定了方法,方法都会被触发执行,而且顺序是由里层向外层传播) 【冒泡】

    1594711400697
    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
      <title>哈哈</title>
      <!-- IMPORT CSS -->
      <link rel="stylesheet" href="css/reset.min.css">
      <style>
        #outer {
          margin: 20px auto;
           300px;
          height: 300px;
          background: pink;
        }
    
        #inner {
          margin: 20px auto;
           200px;
          height: 200px;
          background: cyan;
        }
    
        #center {
          margin: 20px auto;
           100px;
          height: 100px;
          background: orchid;
        }
      </style>
    </head>
    
    <body>
      <div id="outer">
        <div id="inner">
          <div id="center"></div>
        </div>
      </div>
    
      <!-- IMPORT JS -->
      <script>
        /* 大部分事件默认都会存在冒泡传播机制,但是少部分事件天生自己就阻止了冒泡传播 */
    
        // mouseover/mouseout  
        inner.onmouseover = function () {
          console.log('INNER OVER');
        };
        inner.onmouseout = function () {
          console.log('INNER OUT');
        };
        outer.onmouseover = function () {
          console.log('OUTER OVER');
        };
        outer.onmouseout = function () {
          console.log('OUTER OUT');
        };
    
    
        // mouseenter/mouseleave
        inner.onmouseenter = function () {
          console.log('INNER ENTER');
        };
        inner.onmouseleave = function () {
          console.log('INNER LEAVE');
        };
        outer.onmouseenter = function () {
          console.log('OUTER ENTER');
        };
        outer.onmouseleave = function () {
          console.log('OUTER LEAVE');
        };
      </script>
    
    
      <script>
        window.addEventListener('click', function () {
          console.log('WINDOW');
        });
    
        document.addEventListener('click', function () {
          console.log('DOCUMENT');
        });
    
        // HTML
        document.body.addEventListener('click', function () {
          console.log('BODY');
        });
    
        outer.addEventListener('click', function () {
          console.log('OUTER');
        });
    
        inner.addEventListener('click', function () {
          console.log('INNER');
        });
    
        center.addEventListener('click', function (ev) {
          console.log('CENTER');
          // 阻止冒泡传播
          // ev.stopPropagation();
        }); 
      </script>
    </body>
    
    </html>
    

    事件委托

    • 事件委托 / 事件代理:
    • 利用事件的冒泡传播机制,我们把当前元素事件触发要做的事情,全部委托给外层容器,这样触发当前元素的某个事件行为,其外层容器(祖先元素)的相关事件行为也会被触发,再给外层容器事件触发绑定的方法中,基于不同事件源,处理要做的不同的事情。
    • 事件委托/事件代理的优势:
      1. 性能很好(比一般的事件绑定性能提高40%以上,尤其需要单独绑定的元素越多,性能越好)=> 项目中遇到一个容器中很多元素的某个行为触发要做一些事情,此时杜绝一个个的绑定,直接基于事件委托处理(Vue项目中也一样)
      1. 灵活,基于阻止冒泡传播,可以灵活控制哪些走事件代理,哪些不需要走
      1. 某些需求必须基于事件委托来实现:例如点击A、B做什么,剩下不管点击谁都统一做什么...
    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
      <title>哈哈</title>
      <!-- IMPORT CSS -->
      <link rel="stylesheet" href="css/reset.min.css">
      <style>
        #outer {
          margin: 20px auto;
           300px;
          height: 300px;
          background: red;
        }
    
        #inner {
          margin: 20px auto;
           200px;
          height: 200px;
          background: green;
        }
    
        #center {
          margin: 20px auto;
           100px;
          height: 100px;
          background: blue;
        }
      </style>
    </head>
    
    <body>
      <div id="outer">
        <div id="inner">
          <div id="center"></div>
        </div>
      </div>
    
      <!-- IMPORT JS -->
      <script>
        document.body.onclick = function (ev) {
          let target = ev.target,
            id = target.id;
          if (id === "outer") {
            console.log('OUTER');
            return;
          }
          if (id === "inner") {
            console.log('INNER');
            return;
          }
          console.log('啥也不是');
        };
    
        center.onclick = function (ev) {
          console.log('CENTER');
          ev.stopPropagation();
        };
    
    
    
        // 不用事件委托的写法
        center.onclick = function (ev) {
          console.log('CENTER');
          ev.stopPropagation();
        };
        inner.onclick = function (ev) {
          console.log('INNER');
          ev.stopPropagation();
        };
        outer.onclick = function () {
          console.log('OUTER');
        }; 
      </script>
    </body>
    
    </html>
    
  • 相关阅读:
    Google官方教程之Selling In-app Products
    In-app Billing 概述
    Android SDK和ADT无法更新的解决办法
    在NGUI中高效优化UIScrollView之UIWrapContent的简介以及使用
    cocos2d-x 3.1 编译脚本android-build.py
    Storm---DirectGroup(直接分组)
    Lucene Spatial构建地理空间索引
    Log4j2日志配置
    Guava缓存使用
    Maven 多套环境配置
  • 原文地址:https://www.cnblogs.com/jianjie/p/13868379.html
Copyright © 2020-2023  润新知