JavaScript 面試考題:事件委託是什麼

閱讀時間約 16 分鐘
在搞清楚事件委託前必須先了解什麼是事件傳遞機制(Event Propagation),也就是捕獲(capture)跟冒泡(bubble),聽起來好像很難但是我昨天看完彭彭的直播課程(17:00開始)後就懂了!我寫成文章幫助記憶,也希望能幫助到大家,然後希望我面試會被考到。

事件傳遞機制 Event Propagation

事件傳遞機制就是指DOM的事件傳遞,JS中有規定事件會怎麼傳遞。
假設現在有個DOM長成這樣:
<body>
<div id="layout">
<button id="btn">click</button>
</div>
</body>
當我們在畫面中點選<button>時,其實也會同時點到外面的<div>還有<body>,現在加上JS,分別為<button>, <div>, <body>掛上事件:
let div = document.querySelector("#layout");
let btn = document.querySelector("#btn");

document.body.addEventListener(
"click",
function () {
console.log("Body clicked");
}
);
div.addEventListener(
"click",
function () {
console.log("div clicked");
}
);
btn.addEventListener(
"click",
function () {
console.log("btn clicked");
}
);
這時候我們點選了<button>,那他會依照什麼順序印出來呢?
//btn clicked
//div clicked
//Body clicked
他會從內部的<button>開始,接著是<div>,最後是最外層的<body>。
這種由內而外的事件傳遞就是冒泡(bubble);如果是由外而內的話就是捕獲(capture)。

為什麼是冒泡?

因為addEventListener()這個函數其實有三個參數:
addEventListener('事件名稱', 處理函式, 捕獲(true)/冒泡(false))
'事件名稱':例如"click", "scroll”等事件
處理函式:就是經過上面的事件後,要執行什麼function
第三個參數(true/false):第三個參數是布林值,true的話就是捕獲,false的話就是冒泡。
剛剛的範例中,我們並沒有填入第三個參數,因此預設就是false冒泡事件,所以剛剛的範例才會是由內而外的傳遞
如果想改成由外而內的傳遞,那就將函式的第三個參數填上false:
document.body.addEventListener(
"click",
function () {
console.log("Body clicked Capture");
},
true
);
div.addEventListener(
"click",
function () {
console.log("div clicked Capture");
},
true
);
btn.addEventListener(
"click",
function () {
console.log("btn clicked Capture");
},
true
);
那印出來的結果就會是由外而內的捕獲(capture)
//Body clicked Capture
//div clicked Capture
//btn clicked Capture

先捕獲後冒泡

如果把剛剛的範例都寫在一起的話:
let div = document.querySelector("#layout");
let btn = document.querySelector("#btn");

//預設的冒泡(bubble)

document.body.addEventListener(
"click",
function () {
console.log("Body clicked bubble");
}
);
div.addEventListener(
"click",
function () {
console.log("div clicked bubble");
}
);
btn.addEventListener(
"click",
function () {
console.log("btn clicked bubble");
}
);

//捕獲Capture

document.body.addEventListener(
"click",
function () {
console.log("Body clicked Capture");
},
true
);
div.addEventListener(
"click",
function () {
console.log("div clicked Capture");
},
true
);
btn.addEventListener(
"click",
function () {
console.log("btn clicked Capture");
},
true
);
那印出來的結果就會是:
//Body clicked Capture
//div clicked Capture
//btn clicked Capture
//btn clicked bubble
//div clicked bubble
//Body clicked bubble
由外而內的捕獲(Body->div->button)接著再由內而外的冒泡(button->div->body)。

給我取消傳遞事件喔

如果我們想要讓程式取消傳遞事件,例如我希望他捕獲到div(body->div->stop)就停止,這時候只需要加上stopPropagation()
let div = document.querySelector("#layout");
let btn = document.querySelector("#btn");

//預設的冒泡(bubble)

document.body.addEventListener(
"click",
function () {
console.log("Body clicked bubble");
}
);
div.addEventListener(
"click",
function () {
console.log("div clicked bubble");
}
);
btn.addEventListener(
"click",
function () {
console.log("btn clicked bubble");
}
);

//捕獲Capture

document.body.addEventListener(
"click",
function () {
console.log("Body clicked Capture");
},
true
);
div.addEventListener(
"click",
function (e) {
console.log("div clicked Capture");

// 在這裡加上stopPropagation()
e.stopPropagation()
},
true
);
btn.addEventListener(
"click",
function () {
console.log("btn clicked Capture");
},
true
);
function中的參數e:addEventListener()會自動幫忙建立一個事件物件,裡面有該事件的屬性,我們把這個事件物件用參數形式把它抓出來使用。
stopPropagation()加在哪裡就會阻止它繼續往下傳遞,不過這個stopPropagation()並不能阻止同層級的監聽器,例如我們在div上增加兩個addEventListener():
div.addEventListener(
"click",
function (e) {
console.log("div clicked Capture");
e.stopPropagation()
},
true
);

div.addEventListener(
"click",
function () {
console.log("div clicked Capture2");
},
true
);
印出來會是:
//Body clicked Capture
//div clicked Capture
//div clicked Capture2
同一層級的監聽器還是會被執行到。
這時候要改用e.stopImmediatePropagation()
div.addEventListener(
"click",
function (e) {
console.log("div clicked Capture");
e.stopImmediatePropagation()
},
true
);

div.addEventListener(
"click",
function () {
console.log("div clicked Capture2");
},
true
);
那就只會印出第一個console,同層級的監聽器也會被阻止。
//Body clicked Capture
//div clicked Capture

取消預設事件

有時候如果我們要取消瀏覽器的預設,例如阻止a標籤跳轉到別的分頁,那就會用到preventDefault()
<a id="link" href="https://dramanoteblog.com/">Drama Note</a>
let link = document.querySelector("#link");
link.addEventListener("click", function (e) {
e.preventDefault()
console.log("Go to link");
});
加了preventDefault()雖然不會跳轉到該頁面,但是下一個console.log還是會執行,簡單來說preventDefault()只是取消這個DOM元素的預設行為,並不影響事件傳遞,該捕獲該冒泡的都還是會捕獲冒泡。
事件委託 Event Delegation
因為有捕獲跟冒泡事件,於是我們可以執行事件委託。
如果我們有一堆處理類似事件的DOM元素,那我們可以把事件掛在他們的共同父層上,運用e.target還有html的data-自訂屬性,可以快速了解我們是針對哪個DOM元素進行事件處理。
優點:不需要為每一個button寫上程式碼,只須建立一個事件函數並且加入標記即可。

不用一直寫類似的code惹

今天有一段DOM長這樣:
<div id="menu">
<button data-key="add">新增</button>
<button data-key="update">修改</button>
<button data-key="remove">刪除</button>
</div>
我不想在每個<button>上寫重複的click事件了拉!
解決方法:把事件掛在外層的<div>上吧
let menu = document.querySelector("#menu");
menu.addEventListener("click",function(e){
let key = e.target.getAttribute("data-key")
console.log(key)
})
e.target可以抓到我們點擊的DOM元素,例如我們點到新增這個按鈕,就會得到<button data-key=”add”>新增</button>這一串。
getAttribute()用來抓取DOM元素中的屬性值,那我們要抓的是自訂屬性data-key的屬性值,所以點選新增這個按鈕,就能夠get到add。
這樣就能夠區分每個按鈕,也不用重複撰寫同樣的點擊事件程式碼。

第一次用方格子寫技術文章,發現方格子的code區塊還得再加油才行,medium已經支援不同程式語言的code區塊了
avatar-img
2會員
2內容數
前端筆記希望不要再鬼打牆
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
套件(Package)是將程式或程式庫進行組織、分發和共享的一種方式。在軟體開發中,套件通常包含了相關的程式碼、資源文件和元數據,並提供了統一的名稱空間和版本管理。
Thumbnail
當你在開發程式時,難免會遇到各種錯誤和異常情況。這些錯誤可能是因為代碼中的錯誤、外部資源無法訪問或其他不可預期的狀況。為了提高程式的可靠性、穩定性和可維護性,我們使用「例外處理」來處理這些異常情況。
<template> <div id="charge"> <el-card class="box-card"> <ul class="msg-box"> <li> <h4>充值</h4> </li> <li>
Thumbnail
針對 JavaScript 中的原始型別和隱性轉型進行了詳細的探討
Thumbnail
在前端開發中,很常會有需要轉址的需求,且處理的手法滿因人而異的,所以今天就想要來整理一些常見的 JavaScript 頁面轉址方式,以及各自的差異。
Thumbnail
執行以上程式碼,然後看到了這個結果: 為什麼「延遲0秒」的函式寫在上方,但在console印出的結果,它還是被排在第二順位? 利用AC教材提供的youtube演講連結一窺究竟: 演講提供了更多JS的細節概念,身為JS新手的我還在消化,但若針對教案提出的問題來回答,加上利用google大神查到MDN的
Thumbnail
前言 這是第一次寫技術文章,但其實應該也只能說是蒐集很多資料並學習如何透過自己的話解釋的內容,並不能像其他大神可能分享一些很酷的技術,目標就單純是為了完成最後一週的作業(如下)。 走入非同步之前 執行環境(Execution Context) 執行環境堆疊 (Execution stack)
Thumbnail
在一開始學習前端開發的時候,一直遇到講師在課程內容中提到 ES5、ES6 等關鍵字,當初的我,單純認為 ES5、ES6 是講述 JavaScript 的版本,所以在使用上就沒有想太多,反正就是 JavaScript 1.0 、2.0 的感覺吧?
Thumbnail
上篇介紹的promise chain的寫法,是已經比原本好維護了沒錯,但是可讀性似乎還是有點不足,其實還可以改成用async/await的寫法,如下: E 其中,async是非同步的意思,等於是把getData()這個function定義為非同步,因此從console可以看到,test是最先被pri
Thumbnail
輸入畫面 為什麼要做驗證? 因為作為設計者,永遠不該預設使用者會乖乖照設計者的意思輸入。
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
套件(Package)是將程式或程式庫進行組織、分發和共享的一種方式。在軟體開發中,套件通常包含了相關的程式碼、資源文件和元數據,並提供了統一的名稱空間和版本管理。
Thumbnail
當你在開發程式時,難免會遇到各種錯誤和異常情況。這些錯誤可能是因為代碼中的錯誤、外部資源無法訪問或其他不可預期的狀況。為了提高程式的可靠性、穩定性和可維護性,我們使用「例外處理」來處理這些異常情況。
<template> <div id="charge"> <el-card class="box-card"> <ul class="msg-box"> <li> <h4>充值</h4> </li> <li>
Thumbnail
針對 JavaScript 中的原始型別和隱性轉型進行了詳細的探討
Thumbnail
在前端開發中,很常會有需要轉址的需求,且處理的手法滿因人而異的,所以今天就想要來整理一些常見的 JavaScript 頁面轉址方式,以及各自的差異。
Thumbnail
執行以上程式碼,然後看到了這個結果: 為什麼「延遲0秒」的函式寫在上方,但在console印出的結果,它還是被排在第二順位? 利用AC教材提供的youtube演講連結一窺究竟: 演講提供了更多JS的細節概念,身為JS新手的我還在消化,但若針對教案提出的問題來回答,加上利用google大神查到MDN的
Thumbnail
前言 這是第一次寫技術文章,但其實應該也只能說是蒐集很多資料並學習如何透過自己的話解釋的內容,並不能像其他大神可能分享一些很酷的技術,目標就單純是為了完成最後一週的作業(如下)。 走入非同步之前 執行環境(Execution Context) 執行環境堆疊 (Execution stack)
Thumbnail
在一開始學習前端開發的時候,一直遇到講師在課程內容中提到 ES5、ES6 等關鍵字,當初的我,單純認為 ES5、ES6 是講述 JavaScript 的版本,所以在使用上就沒有想太多,反正就是 JavaScript 1.0 、2.0 的感覺吧?
Thumbnail
上篇介紹的promise chain的寫法,是已經比原本好維護了沒錯,但是可讀性似乎還是有點不足,其實還可以改成用async/await的寫法,如下: E 其中,async是非同步的意思,等於是把getData()這個function定義為非同步,因此從console可以看到,test是最先被pri
Thumbnail
輸入畫面 為什麼要做驗證? 因為作為設計者,永遠不該預設使用者會乖乖照設計者的意思輸入。