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>
原文地址:https://www.cnblogs.com/jianjie/p/13868379.html