DOM初窥II DOM事件相关

DOM事件

  1. 事件

    简单来说,事件就是交互动作。要实现用户与页面的交互,先要对目标元素绑定特定的事件、设置事件处理函数,然后用户触发事件,事件处理函数执行,产生交互效果。

  2. DOM事件级别

    DOM级别一共可以分为四个级别:DOM0级、DOM1级、DOM2级和DOM3级。而DOM事件分为3个级别:DOM 0级事件处理,DOM 2级事件处理和DOM 3级事件处理。由于DOM 1级中没有事件的相关内容,所以没有DOM 1级事件。

    1. DOM 0级事件

      1
      2
      3
      4
      5
      
      let btn = document.getElementById('btn')
      btn.onclick = function btnClick(){
          console.log('这个btn被点击了!')
      }
      //btn.onclick = null; 解绑事件
      

      上述代码构造了一个DOM 0级事件,即Id为btn的元素绑定了一个名为onclick的事件,一旦事件(点击btn)触发,就会执行指定的btnClick函数,从而达成既定效果。

      很简单,但是也仅限于此。DOM 0级事件无法使元素绑定多个同类型事件,如果你想实现3、4种不同功能的点击事件,使用这种写法是无法达成的。

    2. DOM 2级事件

      1
      2
      3
      4
      
      let btn = document.getElementById('btn')
      btn.addEventListener("click", btnClick, false);
      btn.addEventListener("click", btnClickAgain, false);
      // btn.removeEventListener('click', btnClick, false); 解绑事件
      

      DOM 2级事件在0级事件的基础上弥补了这种缺陷,允许为同一个事件绑定不同的处理函数,同时定义了.addEventListener().removeEventListener()两种方法,用来绑定/解绑事件。addEventListener()方法一般会接收三个参数addEventListener(eventType,callBack,useCapture),分别表示事件名称类型,可执行的回调函数及是否在捕获阶段执行回调(默认为false)。

    3. DOM 3级事件

      3级事件在2级事件的基础上添加了更多的事件类型,同时允许一些自定义事件存在。

      • UI事件,当用户与页面上的元素交互时触发,如:loadscroll
      • 焦点事件,当元素获得或失去焦点时触发,如:blurfocus
      • 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dblclickmouseup
      • 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
      • 文本事件,当在文档中输入文本时触发,如:textInput
      • 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydownkeypress
      • 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
      • 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified

事件流 & 事件模型

  1. 事件流

    可以尝试把页面理解为一个二维的平面,想象有一张白纸,我们在这张纸上画下了一层一层的同心圆,当我们用手指按住最内的圆圈时,也按住了纸上所有的同心圆,也按住了整张纸。所以单击事件不仅仅发生在被击中的元素上,换句话说,当我们用鼠标单击其中一个元素时,你也是在单击元素的容器,甚至一层一层向外传递(也可以是向内),甚至是在单击整个页面。[1]

    DOM事件模型分为捕获和冒泡两个阶段,一个事件发生后,会在子元素和父元素之间进行传播,这种传播一般分为三个阶段:捕获阶段、目标阶段、冒泡阶段

    eventflow◎ 事件流

  2. 事件捕获

    根据上图,事件捕获即事件从window开始向下进行层层传递,最终到达目标元素。

    1
    2
    3
    
    <div id = "parent">
        <span id = "child">事件捕获</span>
    </div> 
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    let parent = document.getElementById('parent')
    let child = document.getElementById('child')
    
    child.addEventListener('click', function() {
        console.log('是的点的就是我!')
    }, true)
    
    parent.addEventListener('click', function() {
        console.log('parent 向下传递!')
    }, true)
    
    document.body.addEventListener('click', function() {
        console.log('body 向下传递!')
    }, true)
    
    document.addEventListener('click', function() {
        console.log('document 向下传递!')
    }, true)
    
    window.addEventListener('click', function() {
        console.log('window 向下传递!')
    }, true)
    

    capture◎ 事件捕获

  3. 事件冒泡

    事件冒泡与事件捕获正相反,目标元素触发事件后,逐级向上传播

    1
    2
    3
    
    <div id = "parent">
        <span id = "child">事件冒泡</span>
    </div>
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    let parent = document.getElementById('parent')
    let child = document.getElementById('child')
    
    child.addEventListener('click', function() {
       console.log('是的点的就是我!')
    }, false)
    
    parent.addEventListener('click', function() {
       console.log('parent 向上冒泡!')
    }, false)
    
    document.body.addEventListener('click', function() {
       console.log('body 向上冒泡!')
    }, false)
    
    document.addEventListener('click', function() {
       console.log('document 向上冒泡!')
    }, false)
    
    window.addEventListener('click', function() {
       console.log('window 向上冒泡!')
    }, false)
    

    bubbling◎ 事件冒泡

事件对象

  1. Event对象

    在用户触发事件后,执行事件的回调函数时,默认会向该函数传入一个event对象,这个参数记录了该事件的状态和行为。

  2. Event对象属性与方法

    1. Event对象常用方法

      • event.stopPropagation():阻止事件冒泡到父元素,阻止任何父事件处理程序被执行,从而中断冒泡。
      • event.preventDefault():阻止默认事件行为触发。
    2. Event对象常见属性

      • event.type:获取事件类型名称。
      • event.bubbles:指示事件是否冒泡,所有的冒泡都可以取消。
      • event.cancelable:指示是否可以阻止默认事件,与冒泡无关。
      • event.target:获取事件的目标对象(用户交互的)
      • event.currentTarget:获取事件绑定的对象(监听事件的)
      1
      2
      3
      4
      5
      6
      7
      
      <div id = "a">
          <div id="b">
               <div id="c">
                  点击这里
              </div>
          </div>
      </div>
      
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      let a = document.getElementById('a')
      
      a.addEventListener('click', function(e) {
      console.log('e.target.id是:',e.target.id)
      console.log('e.currentTarget.id是:',e.currentTarget.id)
      }, false)
      
      //输出:
      //e.target.id是:c
      //e.currentTarget.id是:a
      //可以明显看出,target为用户点击的目标元素,currentTarget是开发者监听的元素
      

事件委托

  1. 为什么要进行事件委托?

    简单设想一下,这里有一份非常非常长,且随时都会进行增删改操作的列表⬇️

    1
    2
    3
    4
    5
    6
    7
    
    <ul id="list">
        <li>item 1</li>
        <li>item 2</li>
        <li>item 3</li>
        ......
        <li>item 999</li>
    </ul>
    

    现在要求我们点击每个列表项时都会响应一个事件,比如点击后打印出每一项的内容。应该怎么做呢?最简单粗暴的方法莫过于将我们的click事件写满每一项,成为名副其实的“窃听狂魔”,但这也是基于我们目前有的所有项,一旦进行了修改,就要给新增的元素重新绑定事件或者给删去的元素解绑事件……(・_・;光是脑补,就已经是生命不可承受之痛。

    对于机器也是一样的,无数条雷同的事件挤占着内存空间,将会极大地拉低它的运行效率。

    那么有没有什么办法能够一举两得地解决这个问题?想想我们之前写到的,DOM事件模型分为事件捕获、事件冒泡,冒泡,冒泡,像一口煮着粥的大锅,里面不断有气泡生成又消失,但是它们都在锅里————

    它们都在这口锅里!是的,所有的<li>元素都在<ul>这个偌大的容器内,我们只需要给<ul>这个父容器绑定一个适当的、能够实现我们设想的方法,这样一来,无论容器内部的数据发生怎样的变化,只要它们符合我们的要求,都可以根据事件冒泡的传递机制,将绑定在父容器上的事件触发,提供我们想要的事件信息。

    正是由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)又称事件委托。[2]

  2. 实现它!

    1
    2
    3
    4
    5
    6
    7
    
    // 给父容器绑定事件
    document.getElementById('list').addEventListener('click', function (e) {
        if (target.nodeName.toLocaleLowerCase === 'li') {
         // 判断点击的目标元素是否为列表项
            console.log('该项内容为: ', target.innerHTML);
        }
    })
    

文章参考自🥰