Blogger Information
Blog 91
fans 0
comment 0
visits 77331
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
【前端 · 面试 】JavaScript 之你不一定会的基础题(二)
编程三昧
Original
589 people have browsed it

最近我在做前端面试题总结系列,感兴趣的朋友可以添加关注,欢迎指正、交流。

争取每个知识点能够多总结一些,至少要做到在面试时,针对每个知识点都可以侃起来,不至于哑火。

JavaScript 之你不一定会的基础题

前言

在上一篇文章【前端 · 面试 】JavaScript 之你不一定会的基础题(一)中,有同学产生了这样一个疑惑:为什么 click 事件的监听函数中,this.idevent.target.id 的输出值是不一样的?

今天我们就来扒一扒这其中的原理。

题目

有如下的 HTML 文档结构:

<div id="parent">    <div id="child" class="child">        点我    </div></div>

第一次执行如下 JavaScript 代码:

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

第二次执行另一套 JavaScript 代码:

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

问题如下:

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

答案是:

  • 第一次结果为:先弹出“child 事件触发,child”,再弹出“parent 事件触发,parent”。

  • 第二次结果为:先弹出“child 事件触发,child”,再弹出“parent 事件触发,child”。

对于这个答案中的第二次输出结果,有人生出了疑惑:为什么 parent 事件触发时,e.target.id 的结果为 child呢?不应该是 parent 吗?

解惑

DOM 元素事件执行顺序

首先,我们知道,HTML 页面上 DOM 元素的事件执行顺序一般有三个阶段:

  • 事件捕获

  • 事件触发

  • 事件冒泡

整个过程如下图:

image-20210813192245058

事件捕获和事件冒泡

当一个事件发生在具有父元素的元素上(例如,在我们的例子中是 child 元素)时,现代浏览器运行两个不同的阶段 - 捕获阶段和冒泡阶段。 在捕获阶段:

  • 浏览器检查元素的最外层祖先<html>,是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。

  • 然后,它移动到<html>中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。

在冒泡阶段,恰恰相反:

  • 浏览器检查实际点击的元素是否在冒泡阶段中注册了一个onclick事件处理程序,如果是,则运行它

  • 然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达<html>元素。

这两个阶段如下图所示:

bubbling-capturing

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

this 和 event.target

首先,我们得有一个清晰的认知:事件冒泡或者事件捕获,都是针对注册了事件的元素。

关于 this 和 event.target ,总结如下:

  • 在整个事件流程中,event.target 永远都指向真正触发了事件流程的元素 ,即处于事件触阶段的元素。

  • this 是正在执行事件的元素的引用,和 event.currentTarget 指向的元素是一致的,即当前执行的是哪个元素的监听事件,this 和 event.currentTarget 指向的就是哪个元素。

event 还有一个属性 event.srcElement,它是 event.target 的别名,但是是一个非标准属性,尽量不在生产环境中使用。

阻止冒泡

假如有以下代码:

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

当我们点击 child 时,由于事件默认会在冒泡阶段注册,所以,不仅会执行 function2,之后还会执行 function1,这样的结果可能不是我们所期望的,我们更希望它们的点击事件之间互不影响。

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

扩展

现在我们将题目中的 JavaScript 代码再增加一份:

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);

问题1:如果点击 child 元素,输出是什么?

问题2:如果点击 parent 元素,输出是什么?

可以看到,现在 parent 的点击事件是冒泡阶段执行,child 的点击事件是在 捕获阶段执行。

针对问题1,由于 parent 注册的是冒泡阶段执行,所以它的事件是在 child 触发阶段后的冒泡阶段执行的,所以答案应该是:先弹出 “child 事件触发,child”,再弹出“parent 事件触发,child”。

针对问题二,虽然 child 注册的是捕获阶段执行事件,但是 parent 事件流程的捕获根本走不到它,所以答案应该是:只弹出“parent 事件触发,parent”。

总结

上面我们分析了这么多,其实总结起来就下面几条:

  • event.target 指向触发事件流程的元素,且不会改变。

  • this 指向的是当前所执行事件的注册元素。

  • 捕获止于 event.target,冒泡始于 event.target。

  • 主流浏览器都默认在冒泡阶段进行事件注册,所以,只有阻止冒泡的方法而没有阻止捕获的方法。

  • 元素的 addEventListener 方法中的第三个参数是 true 或者 false,对元素自己触发的事件流程都没有任何影响,只有在它的父元素或者子元素在触发相同的事件后才有影响。

小问题也有大根源,勇于发现,勇于探究!

~

~本文完,感谢阅读!


Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post