JavaScript 是一個事件驅動 (Event-driven) 的程式語言,當瀏覽器載入網頁開始讀取後,雖然馬上會讀取 JavaScript 事件相關的程式碼,但是必須等到「事件」被觸發(如使用者點擊、按下鍵盤等)後,才會再進行對應程式的執行。
事件流程可以分成兩種機制:
事件冒泡指的是「從啟動事件的節點 (event.target) 開始,逐層往上傳遞」,直到整個網頁的根節點 document
為止。如下圖:
因此最上層的元素幾乎可以知道內層元素所有發生的事情,其中包含幾個重要的屬性:
event.target
:觸發此事件的元素可以透過 event.target
來取得,這個元素在整個冒泡過程中不會改變。event.currentTarget
:綁定此事件的元素,通常和 this
指的是同一個元素。this
:指的是處理此事件的元素,和 event.currentTarget
指稱同一個元素。如果到某一層你想阻擋事件繼續往下一層傳播,你可以在該層用 event.stopPropagation()
來阻止事件的傳播。但要記得除非有需要,否則不需要停止冒泡事件。
跟Event Bubbling 相反,由上而下的傳遞則稱為「事件捕獲」。Capturing 機制很少在實作中被運用,因此他通常是隱藏起來的。下圖為事件捕獲的順序:
由於元素之間有這樣的上下回報機制,我們的事件才能順利地動起來。
但在實作上,幾乎不會遇到情境需要運用 Capturing,但如果刻意想打開時,可以運用element.addEventListener
的第三個參數useCapture
從原本預設的false
改成true
。
element.addEventListener("click", handler, useCapture)
剛剛我們有提到element.addEventListener
那這個是什麼呢?
我們現在設想一個情境,我要在網頁上設置一個按鈕,當使用者點擊這個按鈕時會印出Hello。
我要用什麼方法讓JavaScript知道我已經點擊按鈕,你必須開始工作了呢?
這時候我們就需要「事件監聽器 (event listener)」,來扮演 HTML 和 JavaScript 之間的接線生。
event handler(事件處理器 )是在特定事件發生時執行的程式碼,用於回應使用者或瀏覽器的操作。事件處理程序通常用於為使用者界面元素(例如按鈕、連結、輸入欄等)添加互動性和功能。
了解了什麼是 event listener
後,我們來看一下語法:
element.addEventListener(type, function, useCapture)
type:這會是一個string,用來指定事件類型,下面會有介紹。
function:當事件被監聽到時要運行的函式,可以直接使用匿名函式。
useCapture:一個布林值,用於指定事件處理程序是在Capturing時(選擇true)還是在Bubbling時(選擇false)被執行。大多數情況下,可以省略這個參數,默認值是 false
。
在都了解後,我們把剛剛設想的情境完成吧:
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('Hello');
});
事件的種類實在太多,以下就列出幾個常見的事件,剩下的就附上MDN網址:
滑鼠事件
鼠標在網頁上滑動時會跨越不同的元素邊界,它的事件設置有豐富的可能性,你可以點擊這裡查看完整的清單,但一般而言,最常見的有以下三種:
click
- 鼠標點擊元素mousemove
- 鼠標滑過元素mouseout
- 鼠標離開元素鍵盤事件
最常用常見的鍵盤事件有:
keydown
- 點擊且長按一個鍵時keyup
- 放開按鍵時完整的清單可看這裡。
表單事件
表單是網頁上獲取使用者回饋的重要元件,當你在網站上使用表單時,你的行為會受到 DOM 物件FormElementInterface
監控。關於表單需要另開一個主題討論,常見事件有:
submit
- 提交表單時focus
- 點擊某個輸入框時input
- 輸入框內容改變時document 事件
DOMContentLoaded
- 當 HTML 下載完成並完整的建立 DOM 模型時觸發就和我們總是在 </body>
前引入 JavaScript 檔案一樣,如果 HTML 還沒下載完就先進行 DOM 操作,勢必會遇到奇怪問題。在實務上除了注意檔案引入位置,在 JavaScript 時還會再包一層 DOMContentLoaded
,確保萬無一失。
其他事件
Event Delegation 是一種在父元素上監聽事件,以便處理子元素事件的技術。
這種方法通常用於在有大量相似子元素的情況下,可以減少事件處理程序的數量,提高效率和性能。
基本思想是,將事件處理程序綁定到父元素,然後利用事件冒泡的特性,當子元素觸發該事件時,事件會一直冒泡到父元素。父元素可以根據觸發事件的子元素來決定如何處理。
下方的code是擷取之前練習todo list的部分片段的code:
// html:
<div id="todo-title">
<h4>Todo</h4>
<ul id="my-todo" class="list-unstyled">
<!-- display todos here -->
</ul>
<h4>Done</h4>
<ul class="done-list list-unstyled"></ul>
</div>
// Delete and check
const todoTitle = document.getElementById('todo-title')
todoTitle.addEventListener("click", function (event) {
const target = event.target;
const parentElement = target.parentElement;
if (target.classList.contains("delete")) {
...
}
});
我們看到我把event listner 放在最外層的,這樣的好處是我們不用掛載很多event listener。
如果我沒有使用這總方法的話,當今天我有100條todo時,我是不是需要掛100個event listener,光想到就覺得累><”
這段code還有一個地方是我們不認識的,那就是const target = event.target
,這段是什麼意思呢?
剛剛那行其實是我們用 event object裡的屬性**target
** ,把最初觸發事件的 DOM 物件賦值在target這個變數裡。
那這個event object是怎麼來的?
當event listener 所監聽的事件發生時,瀏覽器會去執行我們在 addEventListener()
所指定的 function
,也就是Event Handler 。
這個時候,EventListener 會去建立一個「事件物件」 (Event Object),裡面包含了所有與這個事件有關的屬性,並且以「參數」的形式傳給我們的 Event Handler。
其中有三個屬性要特別注意:
target(目標)
用途:需要知道到底我們觸發哪一個 DOM 元素時可以使用
const target = event.target
💡 this
跟 event.target
有點像,但是他們之間的區別是: 隨著 js 冒泡事件的發生,this
是會變化的,但 event.target
不會變化,它永遠是指觸發事件的 DOM 物件
preventDefault
用途:取消它們的預設行為,但並不會阻止事件向上傳遞 (事件冒泡) 。
有使用過的2個時機:
<form>
<button type="submit">送出</button>
</form>
let btn = document.querySelector('button');
btn.addEventListener('click',e =>{
e.preventDefault();
})//點按鈕時,表單不會被提交出去,頁面也不會重刷
stopPropagation()
用途:阻擋事件向上冒泡傳遞
在前半段我們有提到:
如果到某一層你想阻擋事件繼續往下一層傳播,你可以在該層用 event.stopPropagation()
來阻止事件的傳播。
現在我們來看看實際狀況是怎樣吧:
// html
<div id="parent">
<button id="child">點我</button>
</div>
// js
document.getElementById("child").addEventListener("click", function(event) {
alert("子元素的點擊事件觸發了");
event.stopPropagation(); // 阻止事件向上傳播
});
document.getElementById("parent").addEventListener("click", function() {
alert("父元素的點擊事件觸發了");
});
在這個例子中,當點擊子元素時,子元素的點擊事件處理程序會觸發,並顯示一個警示框。同時,父元素的點擊事件處理程序也會觸發,並顯示另一個警示框。但是,由於在子元素的事件處理程序中使用了 stopPropagation(),父元素的事件不會繼續傳播,因此父元素的點擊事件處理程序不會被觸發。
本篇內容是從我之前的學習筆記整理出來的,說真的我搞不懂為什麼我以前的筆記居然有寫到Event inheritance這個東西,不要說當時的我應該根本還不知道什麼是繼承,就算現在你問我我也說得很不完整,我在想應該是網路上的文章直接照抄卻沒有去理解。
繼承的學習我把他安排在最後面,因為我還是有點搞不懂繼承還有原型><”