【前端 · 面試 】JavaScript 之你不必定會的基礎題(二)

最近我在作前端面試題總結系列,感興趣的朋友能夠添加關注,歡迎指正、交流。html

爭取每一個知識點可以多總結一些,至少要作到在面試時,針對每一個知識點均可以侃起來,不至於啞火。前端

JavaScript 之你不必定會的基礎題

前言

在上一篇文章【前端 · 面試 】JavaScript 之你不必定會的基礎題(一)中,有同窗產生了這樣一個疑惑:爲何 click 事件的監聽函數中,this.idevent.target.id 的輸出值是不同的?git

今天咱們就來扒一扒這其中的原理。面試

題目

有以下的 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 代碼:dom

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 代碼的執行結果分別是什麼?post

答案是:學習

  • 第一次結果爲:先彈出「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,對元素本身觸發的事件流程都沒有任何影響,只有在它的父元素或者子元素在觸發相同的事件後纔有影響。

小問題也有大根源,敢於發現,敢於探究!

~

~本文完,感謝閱讀!

~

學習有趣的知識,結識有趣的朋友,塑造有趣的靈魂!

你們好,我是〖編程三昧〗的做者 隱逸王,個人公衆號是『編程三昧』,歡迎關注,但願你們多多指教!

你來,懷揣指望,我有墨香相迎! 你歸,不管得失,惟以餘韻相贈!

知識與技能並重,內力和外功兼修,理論和實踐兩手都要抓、兩手都要硬!

相關文章
相關標籤/搜索