Menu

DOM之事件触发、冒泡、捕获详解

DOM触发事件方法

1.html attribute 标签中添加事件属性

<input value="Click me" onclick="alert('Click!')" type="button">
js逻辑在属性里创建,不是很好,写到一个方法里.
<script>
  function countRabbits() {
    for(let i=1; i<=3; i++) {
      alert("Rabbit number " + i);
    }
  }
</script>

<input type="button" onclick="countRabbits()" value="Count rabbits!">
因为html属性不区分大小写,所以你onClick可以随你写,ONCLICK, onCLICK, 最好是onclick.

2.dom property 元素属性赋值

<input id="elem" type="button" value="Click me">
<script>
  elem.onclick = function() {
    alert('Thank you');
  };
</script>
这个方式直接在dom上绑定,上面的html-attribute是浏览器读取他创建函数对象写入dom中。

处理程序始终位于dom property中:html attribute 只是初始化它的一个方法,并且也不被推荐。

如果html attributre 存在事件绑定,并且dom也存在事件绑定,相同的事件将被dom中的给替代。

直接给元素加事件属性(DOM0级模型)是注册事件最简单的方法,将处理程序赋值给相应的事件属性(事件前面带on)。这种方式适用于所有浏览器。但是缺点是元素将最多只有一个事件处理程序。

dom绑定的一些写法.

<input id="elem" type="button" value="Click me">
<script>
  //1
  elem.onclick = function() {
    alert('Thank you');
  }; 
  
  //2
  function thank() {
    alert('thank you')
  }
  elem.onclick = thank;
</script>

// 但是如果是input中也是不同的。
<input id="elem" type="button" value="Click me" onclick="thank()">

3.addEventListener

事件处理程序的注册方法为addEventListener(),它接受3个参数(事件类型,事件回调函数,布尔值),其中最后一个参数默认值为false,将会把程序注册为冒泡事件处理程序。而其若为true,则为捕获事件处理程序。两者的区别是:若为冒泡,表示在冒泡阶段调用事件处理程序,逐渐冒泡到外层。而捕获恰好相反,在捕获阶段调用处理程序,执行顺序相反。在IE中只支持事件冒泡。移除事件使用removeEventListener(),传参如上。

IE8及以下浏览器不支持此方法,使用attachEvent(on+事件类型, 事件回调函数)方法,只有两个参数(不支持事件捕获)。移除事件使用detachEvent()方法,参数相同。

<button id="elem">Click me</button>

<script>
  elem.addEventListener('click', {
    handleEvent(event) {
      alert(event.type + " at " + event.currentTarget);
    }
  });
</script>
上面的方法使用到了handleEvent, 如果发生事件,handleEvent会被调用。

也可以那么说,当addEventListener接收到处理程序对象时,事件会去调用object.handleEvent(event)

等同于:

<button id="elem">Click me</button>

<script>
  elem.addEventListener('click',function(event) {
      alert(event.type + " at " + event.currentTarget);
  });
</script>

如下,很实用的方法。

<button id="elem">Click me</button>

<script>
  class Menu {
    handleEvent(event) {
      // mousedown -> onMousedown
      let method = 'on' + event.type[0].toUpperCase() + event.type.slice(1);
      this[method](event);
    }

    onMousedown() {
      console.log(1)
    }

    onMouseup() {
      console.log(2)
    }
  }

  let menu = new Menu();
  elem.addEventListener('mousedown', menu);
  elem.addEventListener('mouseup', menu);
</script>

注意:dom添加赋值事件属性方式(普通事件)可以被第二次调用的同一个事件覆盖,而addEventListener事件绑定不会被覆盖,而是会依次执行。

事件流

事件流:事件流是描述从页面中接收事件的顺序。也可以说是事件传播的顺序。

“DOM2事件流”规定的事件流包括三个阶段:

  1. 事件捕获阶段:提供了在事件没有送达目标之前查看事件的机会。与冒泡方向相反,由父向子逐级捕获。
  2. 处于目标阶段:目标对象(元素)本身的时间处理程序调用(执行)。
  3. 事件冒泡阶段。目标元素的事件大部分都会冒泡(传播)到DOM树根。有一些特殊的事件不会冒泡,比如mouseenter和mouseleave事件不会冒泡。若要冒泡,可以使用mouseover和mouseout替代。

事件模型

js包含三种事件模型:DOM0事件模型(始事件模型),DOM2事件模型,IE事件模型

  1. DOM0级模型:事件不会流动传播。事件绑定监听函数方式:① html标签中添加属性如<div onclick="fun()"></div>;② js中获取dom元素后给元素加事件属性:document.onclick = fn,取消监听使用document.onclick = null
  2. IE事件模型:使用attachEvent("onclick", handler)detachEvent("onclick", handler)方法进行绑定事件和移除绑定。事件流只有2个阶段,没有事件捕获阶段。
  3. DOM2级模型:addEventListener()removeEventListener()进行监听和取消。事件流有3个阶段。

事件冒泡

就是一个事件(例如点击),这个点击会往上层元素进行冒泡(往上进行),他是从点击的这个元素(目标元素,即event.target)开始向上去冒泡。

这里开始,先搞清两个点event.targetevent.currentTarget

event.target是当前点击的元素(目标元素)

event.currentTarget也就是this, 是绑定函数操作的元素(addEventListener绑定的元素,有可能是目标元素的父元素)

有的时候你可能并不想他去冒泡,可以通过stopPropagation这个方法去干掉他。

  • event.stopPropagation()

这个方法有个地方需要注意一下,那就是当一个事件有多个处理程序的时候,他只会停止当前程序的冒泡,其他的程序不会收到影响,需要解决这个问题,就需要使用到event.stopImmediatePropagation方法。

尽量不要阻止事件冒泡,除非你知道你自己在干什么。

当你调用return false时会做 3 件事:

  • event.preventDefault() – 它停止浏览器的默认行为。
  • event.stopPropagation() – 它阻止事件传播(或“冒泡”)。
  • 停止回调执行并立即返回。

事件捕获

事件捕获和事件冒泡是不同的,捕获是从上而下。如果需要捕获事件,那就需要将addEventListener的第三个参数(叫做useCapture)设置成true.

默认的false是在冒泡阶段处理事件,true就是捕获阶段处理事件。看下面的代码:

<style>
  body * {
    margin: 10px;
    border: 1px solid blue;
  }
</style>

<form>FORM
  <div>DIV
    <p>P</p>
  </div>
</form>

<script>
  for(let elem of document.querySelectorAll('*')) {
    elem.addEventListener("click", e => alert(`Capturing: ${elem.tagName}`), true);
    elem.addEventListener("click", e => alert(`Bubbling: ${elem.tagName}`));
  }
</script>
运行之后,发现两个顺序是相反的。而且点击的元素是位于捕获阶段的最后,冒泡阶段的开始。

本文固定连接:https://code.zuifengyun.com/2020/03/2105.html,转载须征得作者授权。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

¥ 打赏支持