【前端 · 面试 】JavaScript 之你不一定会的基础题(二)

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

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


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


11.png


前言


在上一篇文章【前端 · 面试 】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 元素的事件执行顺序一般有三个阶段:


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


整个过程如下图:


12.png


事件捕获和事件冒泡


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


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


在冒泡阶段,恰恰相反:


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


这两个阶段如下图所示:


13.png


在现代浏览器中,默认情况下,所有事件处理程序都在冒泡阶段进行注册,这也是为什么只有一个阻止冒泡方法的方法 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,对元素自己触发的事件流程都没有任何影响,只有在它的父元素或者子元素在触发相同的事件后才有影响。


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


~本文完,感谢阅读!


学习有趣的知识,结识有趣的朋友,塑造有趣的灵魂!


你来,怀揣期望,我有墨香相迎! 你归,无论得失,唯以余韵相赠!


知识与技能并重,内力和外功兼修,理论和实践两手都要抓、两手都要硬!



相关文章
|
7天前
|
JavaScript 前端开发
每天一道面试题——JavaScript的this指向【一】
每天一道面试题——JavaScript的this指向【一】
23 0
|
4天前
|
存储 XML 移动开发
前端大厂面试真题
前端大厂面试真题
|
1天前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
44 个 React 前端面试问题
|
5天前
|
前端开发 JavaScript 程序员
聊聊前端 JavaScript 的扩展运算符 “...“ 的使用场景
聊聊前端 JavaScript 的扩展运算符 “...“ 的使用场景
|
7天前
|
存储 JavaScript 前端开发
|
7天前
|
Web App开发 存储 缓存
|
11天前
|
存储 JavaScript 前端开发
后端程序员的前端基础-前端三剑客之JavaScript
后端程序员的前端基础-前端三剑客之JavaScript
13 4
|
11天前
|
前端开发
【前端】校园二手书交易系统javascript+css+html (源码)【独一无二】
【前端】校园二手书交易系统javascript+css+html (源码)【独一无二】
|
5天前
|
前端开发 JavaScript 程序员
前端 JavaScript 的 _ 语法是个什么鬼?
前端 JavaScript 的 _ 语法是个什么鬼?
|
5天前
|
前端开发 JavaScript
前端 JavaScript 与 HTML 怎么实现交互
前端 JavaScript 与 HTML 怎么实现交互