我們不僅可以指定處理常式,還可以從 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 事件類別的簡短清單
UIEventFocusEventMouseEventWheelEventKeyboardEvent- …
如果我們想建立此類事件,應使用這些事件,而不是 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…)