起因
之前我在回想 this 時,突然產生一個想法,也是在開頭就想提的問題,同時你也能問問自己,問題如下:
event handler 中一樣能用 this,這個 this 跟 event.target 一定相同嗎 ?指向的 100% 就是註冊了 event handler的那個 element 嗎?
經過一番釐清之後…
答案是: NO !
具體原因跟 Event Capture (捕捉) & Bubble(冒泡) 有關,下面會一一來說明過程,再來帶入主題 Event Delegation,這邊會預設你已經對 DOM 的 Event 捕捉及冒泡有些許的理解。
釐清問題的過程 & 詳解
既然前面說到跟 Event Capture(捕捉) & Bubble(冒泡) 有關,我們先來個簡單的例子
我們這邊綁定的 event handler 不使用 arrow function,因為在這個 case 下 this 會被自動指向 window,所以先使用一般 function 來觀察,接著先來看點擊 parent div 時,出來的 log 如下,
看起來很正常,如預期的一樣印出相同的 element,再來點擊 child 看看
各位會發現 child log 如預期,但是 parent 卻不同,它的 this 依然指向自己沒錯,可是 event.target 卻指向了 child,其原因是 "先捕捉在冒泡",也就是當一個 event 發生時,背後執行的步驟如下:
- 會先從 window 開始往下一路執行有註冊的 capture event handler
- 到達發生事件的 element 時,再往上一路執行有註冊的 bubble event handler 直到 window
所以 parent 是被 child 的 bubble phase 觸發的,這個 event.target 自然也就落到了 child 上,這也驗證了開頭問題的解答,其實更完整的答案應該是
不使用 arrow function,而使用一般 function 之下時:
- this 永遠會指向註冊了這個 event handler function 的 element。
- event.target 指向的是觸發這次 event 事件流的那個 element。
- 執行的時機,是看這次 event 事件流屬於冒泡還是捕捉。
Event Delegation
透過前面的問題 & 釐清,了解到 event 的傳播是沿著 DOM 的父子層依照樹狀傳遞的,基於這個特性 & event.target 的指向性,我們可以利用它來做 "事件委託 (Delegation)",減少綁定 event handler 的記憶體消耗,看個例子
假設我們有個 table,需求是當某個欄位被點擊到時,該欄位背景色 highlight 成紅色,若直覺去寫可能會寫出下面的樣子
這樣做雖然也可以滿足需求,但很明顯這作法會隨著欄位越多,在記憶體內存有越多重複邏輯的 handler function, 一旦量大將導致效能問題,這時候就可以把 event handler 委託給 td 的父層 table 來處理,如下:
可以看到一樣滿足了欄位背景 highlight 成紅色的需求,但我們並沒有一直在記憶體存入相同邏輯的 function,作法不但更優雅也更省記憶體,唯獨一個小地方要注意的就是,要做 Event Delegation 的父子層 element 不要隔太多層級,不然有可能會沒注意到去觸發其他中間層的 event handler 喔。