在前端開發世界中,JavaScript 和 HTML 之間往往通過 事件 來實現交互。其中多數爲內置事件,本文主要介紹 JS自定義事件概念和實現方式,並結合案例詳細分析自定義事件的原理、功能、應用及注意事項。
📚一、什麼是自定義事件
在日常開發中,我們習慣監聽頁面許多事件,諸如:點擊事件( click
)、鼠標移動事件( mousemove
)、元素失去焦點事件( blur
)等等。
事件本質是一種通信方式,是一種消息,只有在多對象多模塊時,纔有可能需要使用事件進行通信。在多模塊化開發時,可以使用自定義事件進行模塊間通信。
當某些基礎事件無法滿足我們業務,就可以嘗試 自定義事件來解決。
📚二、實現方式介紹
目前實現自定義事件的兩種主要方式是 JS 原生的 Event()
構造函數和 CustomEvent()
構造函數來創建。
1. Event()
Event()
構造函數, 創建一個新的事件對象 Event
。
1.1 語法
let myEvent = new Event(typeArg, eventInit);
1.2 參數
typeArg
: DOMString
類型,表示創建事件的名稱;eventInit
:可選配置項,包括:
字段名稱 | 說明 | 是否可選 | 類型 | 默認值 |
---|---|---|---|---|
bubbles |
表示該事件是否冒泡。 | 可選 | Boolean |
false |
cancelable |
表示該事件能否被取消。 | 可選 | Boolean |
false |
composed |
指示事件是否會在影子DOM根節點之外觸發偵聽器。 | 可選 | Boolean |
false |
1.3 演示示例
// 創建一個支持冒泡且不能被取消的 pingan 事件
let myEvent = new Event("pingan", {"bubbles":true, "cancelable":false});
document.dispatchEvent(myEvent);
// 事件可以在任何元素觸發,不僅僅是document
testDOM.dispatchEvent(myEvent);
1.4 兼容性
圖片來源:https://caniuse.com/
2. CustomEvent()
CustomEvent()
構造函數, 創建一個新的事件對象 CustomEvent
。
2.1 語法
let myEvent = new CustomEvent(typeArg, eventInit);
2.2 參數
typeArg
: DOMString
類型,表示創建事件的名稱;eventInit
:可選配置項,包括:
字段名稱 | 說明 | 是否可選 | 類型 | 默認值 |
---|---|---|---|---|
detail |
表示該事件中需要被傳遞的數據,在 EventListener 獲取。 |
可選 | Any |
null |
bubbles |
表示該事件是否冒泡。 | 可選 | Boolean |
false |
cancelable |
表示該事件能否被取消。 | 可選 | Boolean |
false |
2.3 演示示例
// 創建事件
let myEvent = new CustomEvent("pingan", {
detail: { name: "wangpingan" }
});
// 添加適當的事件監聽器
window.addEventListener("pingan", e => {
alert(`pingan事件觸發,是 ${e.detail.name} 觸發。`);
});
document.getElementById("leo2").addEventListener(
"click", function () {
// 派發事件
window.dispatchEvent(pingan2Event);
}
)
我們也可以給自定義事件添加屬性:
myEvent.age = 18;
2.4 兼容性
圖片來源:https://caniuse.com/
2.5 IE8 兼容
分發事件時,需要使用 dispatchEvent
事件觸發,它在 IE8 及以下版本中需要進行使用 fireEvent
方法兼容:
if(window.dispatchEvent) {
window.dispatchEvent(myEvent);
} else {
window.fireEvent(myEvent);
}
3. Event() 與 CustomEvent() 區別
從兩者支持的參數中,可以看出:Event()
適合創建簡單的自定義事件,而 CustomEvent()
支持參數傳遞的自定義事件,它支持 detail
參數,作爲事件中需要被傳遞的數據,並在 EventListener
獲取。
注意:
當一個事件觸發時,若相應的元素及其上級元素沒有進行事件監聽,則不會有回調操作執行。
當需要對於子元素進行監聽,可以在其父元素進行事件託管,讓事件在事件冒泡階段被監聽器捕獲並執行。此時可以使用 event.target
獲取到具體觸發事件的元素。
📚三、使用場景
事件本質是一種消息,事件模式本質上是觀察者模式的實現,即能用觀察者模式的地方,自然也能用事件模式。
1.場景介紹
比如這兩種場景:
- 場景1:單個目標對象發生改變,需要通知多個觀察者一同改變。
如:當微博列表中點擊“關注”,此時會同時發生很多事:推薦更多類似微博,個人關注數增加…
- 場景2:解耦多模塊開協作。
如:小王負責A模塊開發,小陳負責B模塊開發,模塊B需要模塊A正常運行之後才能執行。
2. 代碼實現
2.1 場景1實現
場景1:單個目標對象發生改變,需要通知多個觀察者一同改變。
本例子模擬三個頁面進行演示:
1.微博列表頁(Weibo.js)
2.粉絲列表頁(User.js)
3.微博首頁(Home.js)
在**微博列表頁(Weibo.js)**中,我們導入其他兩個頁面,並且監聽【關注微博】按鈕的點擊事件,在回調事件中,創建一個自定義事件 focusUser
,並在 document
上使用 dispatchEvent
方法派發自定義事件。
// Weibo.js
import UserModule from "./User.js";
import HomeModule from "./Home.js";
const eventButton = document.getElementById("eventButton");
eventButton.addEventListener("click", event => {
const focusUser = new Event("focusUser");
document.dispatchEvent(focusUser);
})
接下來兩個頁面實現的代碼基本一致,這裏爲了方便觀察,設置了兩者不同輸出日誌。
// User.js
const eventButton = document.getElementById("eventButton");
document.addEventListener("focusUser", event => {
console.log("【粉絲列表頁】監聽到自定義事件觸發,event:",event);
})
// Home.js
const eventButton = document.getElementById("eventButton");
document.addEventListener("focusUser", event => {
console.log("【微博首頁】監聽到自定義事件觸發,event:",event);
})
點擊【關注微博】按鈕後,看到控制檯輸出如下日誌信息:
最終實現了,在 **微博列表頁(Weibo.js)**組件負責派發事件,其他組價負責監聽事件,這樣三個組件之間耦合度非常低,完全不用關係對方,互相不影響。
其實這也是實現了觀察者模式。
2.2 場景2實現
場景2:解耦多模塊開協作。
舉個更直觀的例子,當微博需要加入【一鍵三連】新功能,需要產品原型和UI設計完後,程序員才能開發。
本例子模擬四個模塊:
1.流程控制(Index.js)
2.產品設計(Production.js)
3.UI設計(Design.js)
4.程序員開發(Develop.js)
在流程控制(Index.js)模塊中,我們需要將其他三個流程的模塊都導入進來,然後監聽【開始任務】按鈕的點擊事件,在回調事件中,創建一個自定義事件 startTask
,並在 document
上使用 dispatchEvent
方法派發自定義事件。
// Index.js
import ProductionModule from "./Production.js";
import DesignModule from "./Design.js";
import DevelopModule from "./Develop.js";
const start = document.getElementById("start");
start.addEventListener("click", event => {
console.log("開始執行任務")
const startTask = new Event("startTask");
document.dispatchEvent(startTask);
})
在 Production 產品設計模塊中,監聽任務開始事件 startTask
後,模擬1秒後原型設計完成,並派發一個新的事件 productionSuccess
,開始接下來的UI稿設計。
// Production.js
document.addEventListener("startTask", () => {
console.log("產品開始設計...");
setTimeout(() => {
console.log("產品原型設計完成");
console.log("--------------");
document.dispatchEvent(new Event("productionSuccess"));
}, 1000);
});
在UI稿設計和程序開發模塊,其實也類似,代碼實現:
// Dedign.js
document.addEventListener("productionSuccess", () => {
console.log("UI稿開始設計...");
setTimeout(() => {
console.log("UI稿設計完成");
console.log("--------------");
document.dispatchEvent(new Event("designSuccess"));
}, 1000);
});
// Production.js
document.addEventListener("designSuccess", function (e) {
console.log("開始開發功能...");
setTimeout(function () {
console.log("【一鍵三連】開發完成");
}, 2000)
});
開發完成後,我們點擊【開始任務】按鈕後,看到控制檯輸出如下日誌信息:
最終實現了在 流程控制(Index.js)模塊負責派發事件,其他組件負責監聽事件,按流程完成其他任務。
可以看出,原型設計、UI稿設計和程序開發任務,互不影響,易於任務拓展。
📚四、總結
本文詳細介紹 JS自定義事件概念和實現方式,並結合兩個實際場景進行代碼演示。細心的小夥伴會發現,這兩個實際場景都是用 Event()
構造函數實現,當然也是可以使用 CustomEvent
構造函數來代替。
另外本文也詳細介紹兩種實現方式,包括其區別和兼容性。
最後也希望大家能在實際開發中,多思考代碼解耦,適當使用自定義事件來提高代碼質量。
如有錯誤,歡迎指點。
📚五、參考文章
Author | 王平安 |
---|---|
[email protected] | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787/Leo_Reading/issues |
ES小冊 | js.pingan8787.com |