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,对元素自己触发的事件流程都没有任何影响,只有在它的父元素或者子元素在触发相同的事件后才有影响。