JavaScript 自定義事件如此簡單!

在前端開發世界中,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 兼容性

image.png
圖片來源: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);
  }
)

image.png


我們也可以給自定義事件添加屬性:

myEvent.age = 18;

2.4 兼容性

image.png
圖片來源: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:單個目標對象發生改變,需要通知多個觀察者一同改變。

如:當微博列表中點擊“關注”,此時會同時發生很多事:推薦更多類似微博,個人關注數增加…
image.png

  • 場景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);
})

點擊【關注微博】按鈕後,看到控制檯輸出如下日誌信息:

image.png

最終實現了,在 **微博列表頁(Weibo.js)**組件負責派發事件,其他組價負責監聽事件,這樣三個組件之間耦合度非常低,完全不用關係對方,互相不影響。
其實這也是實現了觀察者模式。

2.2 場景2實現

場景2:解耦多模塊開協作。
舉個更直觀的例子,當微博需要加入【一鍵三連】新功能,需要產品原型和UI設計完後,程序員才能開發。
本例子模擬四個模塊:
1.流程控制(Index.js)
2.產品設計(Production.js)
3.UI設計(Design.js)
4.程序員開發(Develop.js)

image.png

流程控制(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)
});

開發完成後,我們點擊【開始任務】按鈕後,看到控制檯輸出如下日誌信息:

image.png

最終實現了在 流程控制(Index.js)模塊負責派發事件,其他組件負責監聽事件,按流程完成其他任務。
可以看出,原型設計、UI稿設計和程序開發任務,互不影響,易於任務拓展。

📚四、總結

本文詳細介紹 JS自定義事件概念和實現方式,並結合兩個實際場景進行代碼演示。細心的小夥伴會發現,這兩個實際場景都是用 Event() 構造函數實現,當然也是可以使用 CustomEvent 構造函數來代替。
另外本文也詳細介紹兩種實現方式,包括其區別和兼容性。
最後也希望大家能在實際開發中,多思考代碼解耦,適當使用自定義事件來提高代碼質量。

如有錯誤,歡迎指點。

📚五、參考文章

Author 王平安
E-mail [email protected]
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推薦 https://github.com/pingan8787/Leo_Reading/issues
ES小冊 js.pingan8787.com

微信公衆號

bg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章