浏览器捕获、冒泡事件

185 阅读3分钟

浏览器事件模型

浏览器事件模型中的过程主要分为三个阶段:捕获阶段、目标阶段、冒泡阶段。

事件模型.jpg

如何绑定事件

我们有三种方法可以绑定事件,分别是行内绑定,直接赋值,用addEventListener

行内:

<button onclick="handleClick()">Press me</button>

上面这种方法不推荐。

直接赋值

var btn = document.querySelector('button');

btn.onclick = function() {
  console.log('button clicked')
}

上面这种方法不推荐,原因: 这种方法有两个缺点

  1. 不能添加多个同类型的handler
  2. 不能控制在哪个阶段来执行,这个会在后面将事件捕获/冒泡的时候讲到。这个同样可以通过addEventListener来解决。 因此addEventListener横空出世,这个也是目前推荐的写法。

addEventListener

window.addEventListener("click", function (e) {
    console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

它有三个参数:事件名称type、事件具体操作function、捕获/冒泡

捕获:事件流从上而下,假设有三个元素,parent、child、son,如果我点击了son元素,那么parent、child上的元素也会执行。

冒泡:事件流从下而上

   const parent = document.getElementById("parent");
      const child = document.getElementById("child");
      const son = document.getElementById("son");
      window.addEventListener("click", function (e) {
      console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName);
      }, true);

      parent.addEventListener("click", function (e) {
         
          console.log("parent 捕获", e.target.nodeName, e.currentTarget.nodeName);
      }, true);

      child.addEventListener("click", function (e) {
          console.log("child 捕获", e.target.nodeName, e.currentTarget.nodeName);
      }, true);

      son.addEventListener("click", function (e) {
          console.log("son 捕获", e.target.nodeName, e.currentTarget.nodeName);
      }, true);

      window.addEventListener("click", function (e) {
          console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
      }, false);

打印:

image.png 注意:

  • 当第三个参数不传时,默认为false,即冒泡。
  • 考虑浏览器的兼容性,ie浏览器是没有addEventListener的。分别是attachEvent、detachEvent。
  • function的参数e含义:e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素

阻止事件传播

  • e.stopPropagation()

大家经常听到的可能是阻止冒泡,实际上这个方法不只能阻止冒泡,还能阻止捕获阶段的传播。

  • stopImmediatePropagation() 如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了 event.stopImmediatePropagation() 方法,则当前元素剩下的监听函数将不会被执行。
function stopPropagation(ev) {
    if (ev.stopPropagation) {
        ev.stopPropagation(); // 标准w3c
    } else {
        ev.cancelBubble = true; // IE
    }
}

注意:ie不支持事件捕获,它也只能阻止事件冒泡,但是标准w3c是可以阻止捕获的。

取消事件的默认行为

  • e.preventDefault()

e.preventDefault()可以阻止事件的默认行为发生,默认行为是指:点击a标签就转跳到其他页面、拖拽一个图片到浏览器会自动打开、点击表单的提交按钮会提交表单等等,因为有的时候我们并不希望发生这些事情,所以需要阻止默认行为

function preventDefault(event) {
    if (event.preventDefault) {
        event.preventDefault(); // 标准w3c
    } else {
        event.returnValue = false; // IE
    }
}

注意:主要是考虑浏览器的兼容性问题。

  • attachEvent——兼容:IE7、IE8; 不支持第三个参数来控制在哪个阶段发生,默认是绑定在冒泡阶段
  • addEventListener——兼容:firefox、chrome、IE、safari、opera;

事件委托

原理:事件冒泡

 <ul id="ul">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
    </ul>

描述:对于一组元素,我们希望点击对应的元素打印出对应的内容和索引。

思考:你会怎么做呢?这样?

const liList = document.getElementsByTagName("li");

for(let i = 0; i<liList.length; i++){
    liList[i].addEventListener('click', function(e){
        alert(`内容为${e.target.innerHTML}, 索引为${i}`);
   })
}

查找所有的元素,给每个元素添加事件。 思考:这样显然是可以,但是性能可就差了,循环操作dom元素是很耗性能的。

我们应该在ul里面绑定事件:这样!

思考:

  1. 怎么取内容:e.target.innerHTML;
  2. 怎么取索引:index = Array.prototype.indexOf.call(liList, target);
const ul = document.querySelector("ul");
ul.addEventListener('click', function (e) {
    const target = e.target;
  if (target.tagName.toLowerCase() === "li") {
    const liList = this.querySelectorAll("li");
    index = Array.prototype.indexOf.call(liList, target);
    alert(`内容为${target.innerHTML}, 索引为${index}`);
  }
})

注意: Array.prototype.indexOf.call(liList, target)相当于liList.indexOf(target)