JS(七)


(偶然发现一个从前没有注意到的题与其涉及知识点,原理)

DOM 元素事件执行顺序

HTML 页面上 DOM 元素的事件执行顺序一般有三个阶段:

  • 事件捕获
  • 事件触发
  • 事件冒泡

在浏览器中默认执行的是事件冒泡,即我们一般观察不到事件捕获阶段,比如 onclick 等事件。

如果想要观察到事件的捕获阶段,那我们就需要借助 addEventListener 接口来实现。

关于addEventListener()

addEventListener 的基本语法为:

target.addEventListener(type, listener, useCapture);
  • type 事件类型。
  • listener 事件触发实际执行的匿名函数。
  • userCapture 可选,类型为 Boolean,意思是是否执行事件捕获阶段。

关于 listener 中的 this 和 target

  • 当一个 EventListener 在 EventTarget 正在处理事件的时候被注册到 EventTarget 上,它不会被立即触发,但可能在事件流后面的事件触发阶段被触发,例如可能在捕获阶段添加,然后在冒泡阶段被触发。
  • 通常来说 this 的值是触发事件的元素的引用,当使用 addEventListener() 为一个元素注册事件的时候,句柄里的 this 值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样。

一道例题

html 

点击 id 为 child 的 div 后,3段 JavaScript 代码的执行结果分别是什么?

点我
document.getElementById("parent").addEventListener("click", function () {
    alert(`parent 事件触发,` + this.id);
});

document.getElementById("child").addEventListener("click", function () {
    alert(`child 事件触发,` + this.id);
});
  • 先弹出“child 事件触发,child”,再弹出“parent 事件触发,parent”。
document.getElementById("parent").addEventListener("click", function (e) {
    alert(`parent 事件触发,` + e.target.id);
});

document.getElementById("child").addEventListener("click", function (e) {
    alert(`child 事件触发,` + e.target.id);
});
  • 先弹出“child 事件触发,child”,再弹出“parent 事件触发,child”。
document.getElementById("parent").addEventListener("click", function (e) {
    alert(`parent 事件触发,` + e.target.id);
}, true);

document.getElementById("child").addEventListener("click", function (e) {
    alert(`child 事件触发,` + e.target.id);
}, true);
  • 先弹出“parent 事件触发,child”,再弹出“child 事件触发,child”。

 分析:

现代浏览器运行两个不同的阶段 - 捕获阶段和冒泡阶段。 在捕获阶段:

  • 浏览器检查元素的最外层祖先,是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。
  • 然后,它移动到中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。

在冒泡阶段,恰恰相反:

  • 浏览器检查实际点击的元素是否在冒泡阶段中注册了一个onclick事件处理程序,如果是,则运行它
  • 然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达元素。

默认情况下,所有事件处理程序都在冒泡阶段进行注册,这也是为什么只有一个阻止冒泡方法的方法 event.stopPropagation(),而没有阻止捕获的方法,没必要。


  总结:

  • 在整个事件流程中,event.target 永远都指向真正触发了事件流程的元素 ,即处于事件触阶段的元素。
  • this 是正在执行事件的元素的引用,和 event.currentTarget 指向的元素是一致的,即当前执行的是哪个元素的监听事件,this 和 event.currentTarget 指向的就是哪个元素。

阻止冒泡

parent.onclick = function1;
child.onclick = function2;

当我们点击 child 时,由于事件默认会在冒泡阶段注册,所以,不仅会执行 function2,之后还会执行 function1,使它们的点击事件之间互不影响

如果要实现这点,只需要在 function2 中添加 event.stopPropagation() 即可

 扩展

document.getElementById("parent").addEventListener("click", function (e) {
    alert(`parent 事件触发,` + e.target.id);
}, false);

document.getElementById("child").addEventListener("click", function (e) {
    alert(`child 事件触发,` + e.target.id);
}, true);

Q1:如果点击 child 元素,输出是什么?

Q2:如果点击 parent 元素,输出是什么?

 parent 的点击事件 冒泡阶段执行,child 的点击事件是在 捕获阶段执行。

  • 针对Q1,由于 parent 注册的是冒泡阶段执行,所以它的事件是在 child 触发阶段后的冒泡阶段执行的

先弹出 “child 事件触发,child”,再弹出“parent 事件触发,child”

  • 针对Q2,虽然 child 注册的是捕获阶段执行事件,但是 parent 事件流程的捕获无法到达

只弹出“parent 事件触发,parent”


总结

  • event.target 指向触发事件流程的元素,且不会改变。
  • this 指向的是当前所执行事件的注册元素。
  • 捕获止于 event.target,冒泡始于 event.target。
  • 主流浏览器都默认在冒泡阶段进行事件注册,所以,只有阻止冒泡的方法而没有阻止捕获的方法。
  • 元素的 addEventListener 方法中的第三个参数是 true 或者 false,对元素自己触发的事件流程都没有任何影响,只有在它的父元素或者子元素在触发相同的事件后才有影响。