DOM事件相关

DOM 事件

  • 事件是什么?

    在Web应用程序中,事件就是将应用程序内的发生的动作或者发生的事情告知浏览器,浏览器给出响应。

  • 事件和JavaScript有什么关系?

    事件是客户端(浏览器)的一种处理机制,事件机制与JavaScript并不存在存在直接联系,联系的建立是依靠客户端来实现的,事件机制本身并不是JavaScript语言的内容。

    简单来说,DOM事件并不是JavaScript的功能,而是浏览器支持的功能,我们只是使用JavaScript来完成事件的监听和对事件做出响应。

事件绑定

HTML内联绑定事件

HTML中直接绑定DOM,就是在HTML的元素中使用<event>属性来绑定事件,比如onclick这样的on(type)属性,其中type指的就是DOM的事件(比如click),它可以给这个DOM元素绑定一个类型的事件。

<button onclick="show();">Click Me</button> 
<script> 
    function show() { 
    console.log('Show Me!')
    } 
</script>

JavaScript中绑定DOM事件

在JavaScript中绑定DOM事件有两种方法:

  1. element.on(type) = listener

    let btn = document.querySelector('button') 
    btn.onclick = function () { 
        console.log("Show Me!"); //被覆盖
    };
    btn.onclick = function () { 
        console.log("Show Me again!"); //最终绑定
    };
    

    按照DOM事件级别分类,上面的方法是DOM0级事件,就是将一个函数赋值给一个事件处理属性,优点是降低HTML和JS的耦合,并且处理函数可以接收浏览器创建的事件对象event作为参数,缺点在于无法同时给同一个DOM元素绑定多个处理函数,执行上面两次绑定时,下面的函数就会覆盖上面的。

  2. element.addEventListener(type, listener, useCapture)

    参数:

    element:表示要监听事件的目标对象

    type:表示事件类型的字符串,比如click、change、touchstart等

    listener:当指定的事件类型发生时被对知到的一个对象,一般是是一个函数。

    useCapture:设置事件的捕获或者冒泡,它有两个值,其中true表示事件捕获,为false是事件冒泡,默认值为false

    <button>Click Me!</button>
    <script> 
      function show (e) { 
         console.log("Show Me!") 
          console.log(e)
      } 
      let btn = document.querySelector('button') 
      btn.addEventListener('click', show, false)
     </script>
    

    按照DOM事件级别分类,上面的方法是DOM2级事件,优点是可以给同一个DOM元素绑定多个处理函数,并且遵循“先绑定先触发”的原则,处理函数可以接收浏览器创建的事件对象event作为参数。

事件对象

当事件发生时,浏览器会创建一个事件对象,将详细信息放入这个对象当中,下图是把代码中点击事件对象event打印出来的结果。

Event对象在event第一次触发的时候被创建出来,并且一直伴随着事件在DOM结构中流转的整个生命周期(当事件结束后,可以认定为对象也消失了,所有当想一直使用event对象的数据时,可以赋值给其他对象来保留数据)。event对象会被作为第一个参数传递给事件监听的回调函数。我们可以通过这个event对象来获取到大量当前事件相关的信息,下面挑选几个重要的参数:

  • type (String):事件的名称

  • target (node):事件起源的DOM节点,可以理解为用户操作的元素

  • currentTarget?(node):当前回调函数被触发的DOM节点,可以理解为用户监听的元素

    👉详细信息MDN文档

DOM事件流

 <div onclick="parent">
      <div onclick="child">click</div>
 </div>    

上面的代码中,在子元素和父元素上都注册了点击事件,当点击文字时,是先触发父元素上的事件函数,还是子元素上的事件函数呢,这就需要一种约定去规范事件的执行顺序,就是事件执行的流程。 浏览器在发展的过程中出现了两种不同的规范:

  • IE9以下的IE浏览器使用的是事件冒泡,由内向外找监听函数。
  • Netscapte采用的是事件捕获,由外向内找监听函数。
  • 而W3C制定的Web标准中,是同时采用了两种方案,事件捕获和事件冒泡都可以。

W3C事件模型

W3C规范中定义了三个事件传播阶段,依次是捕获阶段目标阶段冒泡阶段

  • 捕获阶段(Capture Phase):事件从window对象自上而下向目标节点传播的阶段;

  • 目标阶段(Target Phase):真正的目标节点正在处理事件的阶段;如果一个事件对象类型被标志为不能冒泡,那么对应的事件对象在到达此阶段时就会终止传播。

  • 冒泡阶段(Bubbling Phase):事件从目标节点自下而上向window对象传播的阶段。

    给DOM元素绑定事件都会经历三个传播阶段,当事件发生时,始终从根开始,沿着路径直到达到目标,然后再重新追溯根源,然后回到根。而其中启动事件的部分和事件从根向下寻找目标称为事件捕获阶段,从目标回到根的阶段为冒泡阶段。

我们可以通过使用事件绑定API指定该父辈事件监听函数是捕获阶段还是冒泡阶段被触发:

  • IE的API:element.attachEvent('oncIick', (n) //指定为冒泡阶段被触发

  • Netscapte的API:element.addEventListener('cIick',fn) / /指定为捕获阶段被触发

  • W3C的API:element.addEventListener('cIick',fn,bool)

    bool没有值或值为falsy——冒泡

    bool值为ture——捕获

事件中断

现实中,很多时候我们并不希望目标元素的事件结束之后还去追溯其根源(冒泡)。在JavaScript中可以在事件对象上使用stopPropagation方法来阻止目标元素的冒泡事件,但是会不阻止默认行为。

  function show (e) { 
     e.stopPropagation();
  }

取消默认事件

  • 浏览器默认事件

许多事件会自动触发浏览器执行某些行为。

例如:点击一个链接 会触发导航(navigation)到该 URL,点击表单的提交按钮 会触发提交到服务器的行为。

  • 取消方法
  1. 使用 event 对象的 event.preventDefault() 方法
<a href="/" onclick="event.preventDefault()">here</a>
  1. return false
<a href="/" onclick="return false">Click here</a>

事件委托

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

适用场景

场景一:给N个同级元素添加点击事件

解决:使用事件委托,在父元素parent上设置监听函数

   <div id="parent">
        <button data-id="1">1</button>
        <button>2</button>
        <button>3</button>
        <button>4</button>
        <button>5</button>
    </div>
    <script>
        parent.addEventListener('click',(e)=>{
            const t=e.target
            if(t.target.toLowerCase()=='button'){
                console.log("button被点击了");
                console.log('button的data-id是'+t.dataset.id);
            }
        })
    </script>

场景二:监听目前还不存在的元素的点击事件

解决:先监听父元素, 等点击的时候看看是不是我想要监听的元素即可

    <div id="parent"> </div>
    <script>
        setTimeout(()=>{
            const button=document.createElement('button')
            button.textContent='button'
            parent.appendChild(button)
        },1000)
        parent.addEventListener('click',(e)=>{
            const t=e.target
            if(t.target.toLowerCase()=='button'){
                console.log("button被点击了");
            }
        })
    </script>

优点

  1. 减少内存消耗,提高性能

    参考场景一,不用单独给每个按钮绑定监听函数,我们只需要给父容器绑定函数即可,这样不管点击的是哪一个后代元素,都会根据冒泡传播的传递机制,把容器的click行为触发,然后把对应的方法执行,根据事件源(target),我们可以知道点击的是哪个子元素,从而完成不同的事。

  2. 动态绑定事件

​ 参考场景二,那么在元素动态发生变化时,就需要给新增的元素绑定事件,给即将删去的元素解绑事件,如果用事件代理就会省去很 多这样麻烦。

参考

事件绑定的姿势

深入理解DOM事件机制