我們不僅可以指定處理常式,還可以從 JavaScript 產生事件。
自訂事件可以用來建立「圖形元件」。例如,我們自己的 JS-based 選單的根元素可能會觸發事件,說明選單發生了什麼事:open
(選單開啟)、select
(選取項目)等等。其他程式碼可能會偵聽事件並觀察選單發生了什麼事。
我們不僅可以產生我們自己發明的新事件,還可以產生內建事件,例如 click
、mousedown
等。這可能有助於自動化測試。
事件建構函式
內建事件類別形成一個階層,類似 DOM 元素類別。根目錄是內建的 Event 類別。
我們可以這樣建立 Event
物件
let event = new Event(type[, options]);
引數
-
type – 事件類型,例如
"click"
或我們自訂的"my-event"
等字串。 -
options – 包含兩個選用屬性的物件
bubbles: true/false
– 若為true
,則事件會冒泡。cancelable: true/false
– 若為true
,則可以防止「預設動作」。稍後我們會了解這對自訂事件的意義。
預設兩者皆為 false:
{bubbles: false, cancelable: false}
。
dispatchEvent
建立事件物件後,我們應該使用呼叫 elem.dispatchEvent(event)
在元素上「執行」它。
然後處理常式會對它做出反應,就像它是一般瀏覽器事件一樣。如果事件是用 bubbles
旗標建立的,則它會冒泡。
在以下範例中,click
事件在 JavaScript 中啟動。處理常式的工作方式就像按鈕被點擊一樣
<button id="elem" onclick="alert('Click!');">Autoclick</button>
<script>
let event = new Event("click");
elem.dispatchEvent(event);
</script>
有一種方法可以區分「真實」使用者事件和指令碼產生的事件。
屬性 event.isTrusted
對來自真實使用者動作的事件為 true
,對指令碼產生的事件為 false
。
冒泡範例
我們可以用名稱 "hello"
建立一個冒泡事件,並在 document
上擷取它。
我們只需要將 bubbles
設定為 true
<h1 id="elem">Hello from the script!</h1>
<script>
// catch on document...
document.addEventListener("hello", function(event) { // (1)
alert("Hello from " + event.target.tagName); // Hello from H1
});
// ...dispatch on elem!
let event = new Event("hello", {bubbles: true}); // (2)
elem.dispatchEvent(event);
// the handler on document will activate and display the message.
</script>
注意事項
- 我們應該對自訂事件使用
addEventListener
,因為on<event>
只存在於內建事件,document.onhello
無效。 - 必須設定
bubbles:true
,否則事件不會冒泡。
冒泡機制對內建 (click
) 和自訂 (hello
) 事件相同。也有擷取和冒泡階段。
MouseEvent、KeyboardEvent 等
以下是 UI 事件規格 中 UI 事件類別的簡短清單
UIEvent
FocusEvent
MouseEvent
WheelEvent
KeyboardEvent
- …
如果我們想建立此類事件,應使用這些事件,而不是 new Event
。例如,new MouseEvent("click")
。
正確的建構函數允許為該類型的事件指定標準屬性。
例如,滑鼠事件的 clientX/clientY
let event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
alert(event.clientX); // 100
請注意:一般性的 Event
建構函數不允許這樣做。
讓我們試試看
let event = new Event("click", {
bubbles: true, // only bubbles and cancelable
cancelable: true, // work in the Event constructor
clientX: 100,
clientY: 100
});
alert(event.clientX); // undefined, the unknown property is ignored!
技術上,我們可以在建立後直接指定 event.clientX=100
來解決這個問題。因此,這是一個方便性和遵循規則的問題。瀏覽器產生的事件始終具有正確的類型。
不同 UI 事件的完整屬性清單在規格中,例如,MouseEvent。
自訂事件
對於我們自己的全新事件類型,例如 "hello"
,我們應該使用 new CustomEvent
。技術上,CustomEvent 與 Event
相同,只有一個例外。
在第二個參數(物件)中,我們可以新增一個額外的屬性 detail
,用於傳遞我們想隨事件傳遞的任何自訂資訊。
例如
<h1 id="elem">Hello for John!</h1>
<script>
// additional details come with the event to the handler
elem.addEventListener("hello", function(event) {
alert(event.detail.name);
});
elem.dispatchEvent(new CustomEvent("hello", {
detail: { name: "John" }
}));
</script>
detail
屬性可以包含任何資料。技術上,我們可以不用它,因為我們可以在建立常規 new Event
物件後將任何屬性指定給它。但是,CustomEvent
提供了特殊的 detail
欄位,以避免與其他事件屬性發生衝突。
此外,事件類別描述了「事件的種類」,如果事件是自訂的,那麼我們應該使用 CustomEvent
,以清楚說明事件的種類。
event.preventDefault()
許多瀏覽器事件都有「預設動作」,例如導覽至連結、開始選取等等。
對於新的自訂事件,絕對沒有預設的瀏覽器動作,但是觸發事件的程式碼可能會在觸發事件後有自己的計畫要執行。
透過呼叫 event.preventDefault()
,事件處理常式可以發出一個訊號,表示應取消這些動作。
在這種情況下,呼叫 elem.dispatchEvent(event)
會傳回 false
。而觸發事件的程式碼知道它不應該繼續執行。
讓我們來看一個實際的範例 - 隱藏兔子(可能是關閉選單或其他東西)。
下方你可以看到一個 #rabbit
和 hide()
函式,它會在函式中發送 "hide"
事件,讓所有有興趣的方知道兔子將要躲起來。
任何處理常式都可以使用 rabbit.addEventListener('hide',...)
聆聽該事件,並且在需要時使用 event.preventDefault()
取消動作。這樣兔子就不會消失。
<pre id="rabbit">
|\ /|
\|_|/
/. .\
=\_Y_/=
{>o<}
</pre>
<button onclick="hide()">Hide()</button>
<script>
function hide() {
let event = new CustomEvent("hide", {
cancelable: true // without that flag preventDefault doesn't work
});
if (!rabbit.dispatchEvent(event)) {
alert('The action was prevented by a handler');
} else {
rabbit.hidden = true;
}
}
rabbit.addEventListener('hide', function(event) {
if (confirm("Call preventDefault?")) {
event.preventDefault();
}
});
</script>
請注意:事件必須有 cancelable: true
旗標,否則 event.preventDefault()
呼叫會被忽略。
事件中的事件是同步的
事件通常會在佇列中處理。也就是說:如果瀏覽器正在處理 onclick
,而新的事件發生,例如滑鼠移動,那麼其處理會排隊,對應的 mousemove
處理常式會在 onclick
處理完成後呼叫。
值得注意的例外情況是,當一個事件從另一個事件中啟動時,例如使用 dispatchEvent
。此類事件會立即處理:新的事件處理常式會被呼叫,然後目前的事件處理會恢復。
例如,在以下程式碼中,menu-open
事件會在 onclick
期間觸發。
它會立即處理,而不用等到 onclick
處理常式結束
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
}));
alert(2);
};
// triggers between 1 and 2
document.addEventListener('menu-open', () => alert('nested'));
</script>
輸出的順序是:1 → 巢狀 → 2。
請注意,巢狀事件 menu-open
會在 document
上被捕捉。巢狀事件的傳播和處理會在處理回到外部程式碼 (onclick
) 之前完成。
這不只與 dispatchEvent
有關,還有其他情況。如果事件處理常式呼叫會觸發其他事件的方法,它們也會以巢狀方式同步處理。
假設我們不喜歡這樣。我們希望 onclick
能夠先完全處理,獨立於 menu-open
或任何其他巢狀事件。
然後我們可以將 dispatchEvent
(或其他觸發事件的呼叫) 放在 onclick
的最後面,或者,也許更好的是,將它包在零延遲的 setTimeout
中
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
})));
alert(2);
};
document.addEventListener('menu-open', () => alert('nested'));
</script>
現在 dispatchEvent
會在目前的程式碼執行完成後非同步執行,包括 menu.onclick
,因此事件處理常式是完全分開的。
輸出順序變為:1 → 2 → nested。
摘要
要從程式碼產生事件,我們首先需要建立一個事件物件。
通用的 Event(name, options)
建構函式接受一個任意的事件名稱和具有兩個屬性的 options
物件
- 如果事件應該冒泡,則為
bubbles: true
。 - 如果
event.preventDefault()
應該運作,則為cancelable: true
。
其他原生事件的建構函式,例如 MouseEvent
、KeyboardEvent
等,接受特定於該事件類型的屬性。例如,滑鼠事件的 clientX
。
對於自訂事件,我們應該使用 CustomEvent
建構函式。它有一個名為 detail
的額外選項,我們應該將事件特定資料指定給它。然後所有處理常式都可以將其存取為 event.detail
。
儘管有產生瀏覽器事件(例如 click
或 keydown
)的技術可能性,但我們應該非常小心地使用它們。
我們不應該產生瀏覽器事件,因為這是一種執行處理常式的駭客方式。這在大部分時間都是糟糕的架構。
原生事件可能會產生
- 作為一種骯髒的駭客方式,讓第三方程式庫以所需的方式運作,如果它們沒有提供其他互動方式。
- 對於自動化測試,在指令碼中「按一下按鈕」並查看介面是否正確反應。
具有我們自己名稱的自訂事件通常會為架構目的而產生,以表示我們的選單、滑塊、輪播等內部發生了什麼事。
留言
<code>
標籤,對於多行程式碼,請將它們包覆在<pre>
標籤中,對於超過 10 行的程式碼,請使用沙盒 (plnkr、jsbin、codepen…)