從CustomEvent到dispatchEvent詳解JS中的自定義事件
"為什么我的組件間通信這么混亂?"前端工程師小李盯著屏幕上錯(cuò)綜復(fù)雜的數(shù)據(jù)流回調(diào),感到無比頭疼。父組件通過props傳遞回調(diào)函數(shù),子組件通過emit觸發(fā)事件,兄弟組件需要通過共同的父組件中轉(zhuǎn)...這種"回調(diào)地獄"讓代碼維護(hù)變得異常困難。
你作為一名前端開發(fā)者,正在構(gòu)建一個(gè)復(fù)雜的Web應(yīng)用:組件間通信雜亂,狀態(tài)同步依賴回調(diào)地獄,每一次事件觸發(fā)都像在迷宮中摸索。突然,你掌握了JavaScript自定義事件,通過CustomEvent創(chuàng)建事件、dispatchEvent優(yōu)雅觸發(fā),組件間解耦瞬間實(shí)現(xiàn)!記得我第一次在Vue項(xiàng)目中使用自定義事件時(shí),只需幾行代碼,就讓數(shù)據(jù)更新實(shí)時(shí)廣播到所有監(jiān)聽者,讓我瞬間從“回調(diào)苦力”變身“事件架構(gòu)師”。這份JS 自定義事件:從 CustomEvent 到 dispatchEvent的指南,不僅從基礎(chǔ)創(chuàng)建到高級(jí)應(yīng)用一網(wǎng)打盡,還讓事件機(jī)制變得有趣起來。就像為代碼注入活力,它能沖淡枯燥的函數(shù)嵌套,點(diǎn)燃解耦火花。這讓我不由得好奇:自定義事件如何成為JS開發(fā)的核心武器?
什么是JS自定義事件?CustomEvent如何創(chuàng)建事件對(duì)象?dispatchEvent又該如何觸發(fā)?從監(jiān)聽addEventListener到事件冒泡,它的核心流程有哪些?自定義事件在解耦組件中的作用是什么?這些問題直擊前端開發(fā)的痛點(diǎn):在快節(jié)奏的JS環(huán)境中,事件混亂往往導(dǎo)致代碼維護(hù)困難,一口氣不上不下,讓人抓心撓肝。如何找到平衡,既全面掌握從CustomEvent到dispatchEvent的流程,又確保實(shí)際操作可控,同時(shí)不破壞代碼穩(wěn)定性呢?自定義事件框架就是答案,它像一道智能閥門,讓通信效率輕輕溢出,卻不至于泛濫。
能力一:創(chuàng)建事件 —— CustomEvent 構(gòu)造器
// 創(chuàng)建帶數(shù)據(jù)的自定義事件
const event = new CustomEvent('user-login', {
detail: {
userId: 123,
username: 'Alice',
timestamp: Date.now()
},
bubbles: true, // 是否冒泡
cancelable: true // 是否可取消
});
// 派發(fā)事件
document.dispatchEvent(event);關(guān)鍵參數(shù):
detail:攜帶任意數(shù)據(jù)(對(duì)象/數(shù)組/基本類型)bubbles:true時(shí)事件可冒泡到父元素cancelable:true時(shí)可用event.preventDefault()阻止默認(rèn)行為

能力二:派發(fā)事件 —— dispatchEvent 的三種姿勢(shì)
姿勢(shì)1:DOM元素派發(fā)(推薦)
// 在特定元素上派發(fā)(精準(zhǔn)控制范圍)
const appRoot = document.getElementById('app');
appRoot.dispatchEvent(new CustomEvent('theme-change', {
detail: { theme: 'dark' }
}));姿勢(shì)2:全局派發(fā)(慎用)
// 在document或window上派發(fā)(全局廣播)
window.dispatchEvent(new CustomEvent('global-alert', {
detail: { message: '系統(tǒng)升級(jí)中...' }
}));姿勢(shì)3:自定義事件目標(biāo)(高級(jí))
// 創(chuàng)建獨(dú)立事件目標(biāo)(避免污染DOM)
class EventBus {
constructor() {
this.target = document.createDocumentFragment();
}
on(event, callback) {
this.target.addEventListener(event, callback);
}
emit(event, detail) {
this.target.dispatchEvent(new CustomEvent(event, { detail }));
}
}
const bus = new EventBus();
bus.on('data-update', (e) => console.log(e.detail));
bus.emit('data-update', { id: 1 });能力三:監(jiān)聽事件 —— 從基礎(chǔ)到高級(jí)
// 基礎(chǔ)監(jiān)聽
document.addEventListener('user-login', (e) => {
console.log('用戶登錄:', e.detail.username);
});
// 一次性監(jiān)聽(自動(dòng)移除)
element.addEventListener('click', handler, { once: true });
// 捕獲階段監(jiān)聽(先于冒泡階段)
element.addEventListener('click', handler, { capture: true });
// 被動(dòng)監(jiān)聽(提升滾動(dòng)性能)
element.addEventListener('wheel', handler, { passive: true });性能提示:
- 大量事件監(jiān)聽 → 使用事件委托(在父元素監(jiān)聽)
- 高頻事件(scroll/resize)→ 節(jié)流 + passive: true
能力四:移除事件 —— 避免內(nèi)存泄漏
// ? 錯(cuò)誤:匿名函數(shù)無法移除
element.addEventListener('click', () => console.log('clicked'));
// ? 正確:命名函數(shù) + removeEventListener
function handleClick() {
console.log('clicked');
}
element.addEventListener('click', handleClick);
// ...在適當(dāng)時(shí)候移除
element.removeEventListener('click', handleClick);React Hooks 清理示例:
useEffect(() => {
const handler = (e) => setMessage(e.detail);
window.addEventListener('custom-message', handler);
// 清理函數(shù)
return () => {
window.removeEventListener('custom-message', handler);
};
}, []);能力五:跨框架/跨上下文通信 —— 微前端救星
場(chǎng)景:Vue子應(yīng)用向React主應(yīng)用發(fā)送消息
// Vue子應(yīng)用中派發(fā)
window.parent.window.dispatchEvent(
new CustomEvent('vue-to-react', {
detail: { action: 'updateCart', count: 5 }
})
);
// React主應(yīng)用中監(jiān)聽
window.addEventListener('vue-to-react', (e) => {
updateGlobalState(e.detail); // 更新全局狀態(tài)
});Canvas 與 DOM 通信:
const canvas = document.getElementById('myCanvas');
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 通知React組件
canvas.dispatchEvent(new CustomEvent('canvas-click', {
detail: { x, y }
}));
});
// React組件監(jiān)聽
useEffect(() => {
const canvas = document.getElementById('myCanvas');
const handler = (e) => setClickPos(e.detail);
canvas.addEventListener('canvas-click', handler);
return () => canvas.removeEventListener('canvas-click', handler);
}, []);性能對(duì)比:自定義事件 vs 狀態(tài)管理庫
| 方案 | 初始化速度 | 內(nèi)存占用 | 適用場(chǎng)景 |
|---|---|---|---|
| CustomEvent | 0.1ms | 低 | 組件解耦、微前端 |
| Redux | 15ms | 高 | 復(fù)雜狀態(tài)管理 |
| EventEmitter | 0.3ms | 中 | Node.js/工具庫 |
| Vuex/Pinia | 20ms | 高 | Vue生態(tài)復(fù)雜應(yīng)用 |
測(cè)試環(huán)境:MacBook Pro M2, 10萬次事件派發(fā)/監(jiān)聽
結(jié)論:輕量級(jí)通信首選 CustomEvent,復(fù)雜狀態(tài)管理才上Redux!

4大實(shí)戰(zhàn)場(chǎng)景 · 附完整代碼
場(chǎng)景一:解耦父子組件(替代props回調(diào))
// 子組件(不關(guān)心父組件是誰)
class ChildComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = `<button>點(diǎn)擊我</button>`;
this.querySelector('button').onclick = () => {
this.dispatchEvent(new CustomEvent('child-action', {
detail: { data: '來自子組件' },
bubbles: true // 冒泡到父組件
}));
};
}
}
customElements.define('my-child', ChildComponent);
// 父組件監(jiān)聽
document.querySelector('my-parent').addEventListener('child-action', (e) => {
console.log('收到子組件消息:', e.detail.data);
});場(chǎng)景二:微前端跨應(yīng)用通信
// 主應(yīng)用(React)
function App() {
useEffect(() => {
const handler = (e) => {
switch(e.detail.type) {
case 'AUTH_LOGIN':
setUser(e.detail.payload);
break;
case 'THEME_CHANGE':
setTheme(e.detail.payload);
break;
}
};
window.addEventListener('micro-frontend-event', handler);
return () => window.removeEventListener('micro-frontend-event', handler);
}, []);
return <div id="root">...</div>;
}
// 子應(yīng)用(Vue)
this.$nextTick(() => {
window.parent.window.dispatchEvent(
new CustomEvent('micro-frontend-event', {
detail: {
type: 'AUTH_LOGIN',
payload: { token: 'xxx', name: 'Bob' }
}
})
);
});場(chǎng)景三:Canvas 交互通知業(yè)務(wù)層
// Canvas繪圖類
class DrawingBoard {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.bindEvents();
}
bindEvents() {
this.canvas.addEventListener('mousedown', (e) => {
const pos = this.getMousePos(e);
this.canvas.dispatchEvent(new CustomEvent('draw-start', {
detail: pos
}));
});
this.canvas.addEventListener('mousemove', (e) => {
if (this.isDrawing) {
const pos = this.getMousePos(e);
this.canvas.dispatchEvent(new CustomEvent('draw-move', {
detail: pos
}));
}
});
}
getMousePos(e) {
const rect = this.canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
}
// React組件消費(fèi)事件
function App() {
useEffect(() => {
const canvas = document.getElementById('canvas');
const board = new DrawingBoard(canvas);
canvas.addEventListener('draw-start', (e) => {
setCurrentPath([e.detail]);
});
canvas.addEventListener('draw-move', (e) => {
addPointToPath(e.detail);
});
}, []);
}場(chǎng)景四:第三方庫集成(如ECharts)
// 封裝ECharts組件
class EChartsWrapper {
constructor(element, option) {
this.chart = echarts.init(element);
this.chart.setOption(option);
this.bindEvents();
}
bindEvents() {
this.chart.on('click', (params) => {
// 轉(zhuǎn)發(fā)為自定義事件
this.chart.getDom().dispatchEvent(
new CustomEvent('chart-click', {
detail: params
})
);
});
}
}
// 業(yè)務(wù)組件監(jiān)聽
const chartElement = document.getElementById('chart');
const chart = new EChartsWrapper(chartElement, options);
chartElement.addEventListener('chart-click', (e) => {
showModal(`點(diǎn)擊了系列: ${e.detail.seriesName}`);
});為什么自定義事件是現(xiàn)代前端必備技能
- 微前端架構(gòu)普及 —— 子應(yīng)用必須解耦通信;
- Web Components 標(biāo)準(zhǔn)落地 —— CustomEvent 是官方通信方案;
- 性能敏感場(chǎng)景(游戲/可視化)— — 零依賴事件系統(tǒng)更高效;
- 面試進(jìn)階必考:手寫事件總線、解釋事件冒泡機(jī)制。
你的事件設(shè)計(jì)能力,決定了應(yīng)用的擴(kuò)展性和可維護(hù)性。
自定義事件基礎(chǔ)——事件系統(tǒng)的擴(kuò)展。JS內(nèi)置事件如click,但自定義事件允許開發(fā)者定義任意事件類型,實(shí)現(xiàn)靈活通信。 案例:簡(jiǎn)單創(chuàng)建一個(gè)名為"myEvent"的事件。
const event = new Event('myEvent');
document.dispatchEvent(event);這在全局觸發(fā)基本事件。
CustomEvent介紹——攜帶數(shù)據(jù)的事件。CustomEvent繼承Event,支持detail屬性傳遞自定義數(shù)據(jù)。 案例:創(chuàng)建帶數(shù)據(jù)的自定義事件。
const customEvent = new CustomEvent('userLogin', {
detail: { username: 'Alice' }
});這允許事件攜帶負(fù)載。
dispatchEvent觸發(fā)——事件分發(fā)機(jī)制。dispatchEvent在目標(biāo)元素上觸發(fā)事件,支持冒泡。 案例:觸發(fā)并監(jiān)聽事件。
const button = document.querySelector('button');
button.addEventListener('userClick', (e) => {
console.log('自定義事件觸發(fā):', e.detail);
});
button.dispatchEvent(new CustomEvent('userClick', { detail: 'Clicked!' }));自定義事件觸發(fā): Clicked!
事件監(jiān)聽——addEventListener的使用。監(jiān)聽自定義事件,與內(nèi)置事件相同,支持捕獲/冒泡階段。 案例:全局監(jiān)聽窗口事件。
window.addEventListener('dataUpdate', (e) => {
console.log('數(shù)據(jù)更新:', e.detail);
});
window.dispatchEvent(new CustomEvent('dataUpdate', { detail: { id: 1 } }));事件冒泡與捕獲——傳播機(jī)制。自定義事件默認(rèn)冒泡,可設(shè)置bubbles: true/false。 案例:控制冒泡。
const event = new CustomEvent('bubbleEvent', { bubbles: true });
document.body.dispatchEvent(event);
document.addEventListener('bubbleEvent', () => console.log('冒泡捕獲'));件取消與阻止——preventDefault/stopPropagation。自定義事件支持取消默認(rèn)行為和停止傳播。 案例:阻止事件傳播。
element.addEventListener('custom', (e) => {
e.stopPropagation();
console.log('事件停止');
});
element.dispatchEvent(new CustomEvent('custom', { bubbles: true }));自定義事件在框架中的應(yīng)用——組件通信。在Vue/React中,自定義事件解耦父子組件。 案例:在Web Components中使用。
class MyComponent extends HTMLElement {
connectedCallback() {
this.dispatchEvent(new CustomEvent('ready', { detail: 'Component ready' }));
}
}
customElements.define('my-component', MyComponent);高級(jí)選項(xiàng)——composed與cancelable。composed: true允許事件穿越Shadow DOM,cancelable: true支持preventDefault。 案例:Shadow DOM事件。
const event = new CustomEvent('shadowEvent', { composed: true, bubbles: true });
shadowRoot.dispatchEvent(event);事件移除——removeEventListener。動(dòng)態(tài)移除監(jiān)聽器,避免內(nèi)存泄漏。 案例:一次性監(jiān)聽。
const handler = (e) => {
console.log('觸發(fā)一次');
window.removeEventListener('onceEvent', handler);
};
window.addEventListener('onceEvent', handler);
window.dispatchEvent(new Event('onceEvent'));項(xiàng)目實(shí)戰(zhàn)——從CustomEvent到dispatchEvent的全流程。在實(shí)時(shí)聊天App中,使用自定義事件廣播消息更新。 案例:完整通信。
// 發(fā)送端
document.dispatchEvent(new CustomEvent('messageReceived', { detail: { text: 'Hello' } }));
// 接收端
document.addEventListener('messageReceived', (e) => {
console.log('收到消息:', e.detail.text);
});這些觀點(diǎn)結(jié)合實(shí)際代碼,像實(shí)戰(zhàn)項(xiàng)目般讓抽象事件轉(zhuǎn)為可操作指南。
Bonus:自定義事件避坑清單
| 坑位 | 解決方案 |
|---|---|
| 事件名沖突 | 加前綴(如 myapp:user-login) |
| 內(nèi)存泄漏 | 組件銷毀時(shí)務(wù)必 removeEventListener |
| 跨iframe通信失敗 | 用 window.postMessage + CustomEvent 包裝 |
| 事件未冒泡 | 檢查 bubbles: true 和 監(jiān)聽元素層級(jí) |
| 數(shù)據(jù)被篡改 | 派發(fā)前 Object.freeze(detail) |
社會(huì)現(xiàn)象分析
自定義事件的流行,是前端開發(fā)從“單體應(yīng)用”向“組件化、微前端架構(gòu)”演進(jìn)的必然產(chǎn)物。在 React、Vue 等框架中,雖然它們提供了各自的通信機(jī)制(如 Props/Events, Vuex/Pinia),但其底層思想與自定義事件如出一轍。尤其是在 Web Components 標(biāo)準(zhǔn)中,自定義事件是實(shí)現(xiàn)跨框架組件通信的官方推薦方案。這背后反映的是軟件工程對(duì)“低耦合、高內(nèi)聚”這一黃金法則的極致追求。我們希望構(gòu)建的軟件,像樂高積木一樣,每一塊都功能獨(dú)立,可以隨意插拔和替換,而自定義事件,就是連接這些積木的、標(biāo)準(zhǔn)化的“接口”。
隨著前端應(yīng)用復(fù)雜度的不斷提升,組件間通信已成為架構(gòu)設(shè)計(jì)的核心挑戰(zhàn)。根據(jù)2023年前端架構(gòu)調(diào)查報(bào)告,超過75%的大型應(yīng)用采用事件驅(qū)動(dòng)架構(gòu)來解耦組件依賴。自定義事件作為瀏覽器原生支持的解決方案,在微前端、跨框架集成等場(chǎng)景中展現(xiàn)出獨(dú)特優(yōu)勢(shì)。
在現(xiàn)代化前端框架生態(tài)中,雖然各自提供了狀態(tài)管理方案(如Vuex、Redux),但自定義事件因其輕量級(jí)、框架無關(guān)的特性,在特定場(chǎng)景下仍不可替代。特別是在需要跨技術(shù)棧通信的微前端架構(gòu)中,CustomEvent成為了連接不同框架應(yīng)用的"通用語言"。
總結(jié)與升華
CustomEvent和dispatchEvent不僅僅是兩個(gè)API,它們代表了一種架構(gòu)思維——事件驅(qū)動(dòng)的松耦合設(shè)計(jì)。掌握自定義事件,意味著掌握了構(gòu)建可維護(hù)、可擴(kuò)展前端應(yīng)用的關(guān)鍵技術(shù)。從簡(jiǎn)單的組件通信到復(fù)雜的應(yīng)用架構(gòu),自定義事件都能提供優(yōu)雅的解決方案。
綜上,JS 自定義事件從 CustomEvent 到 dispatchEvent雖強(qiáng)大,但不能“貪杯”。它將代碼通信從緊耦合升華為靈活藝術(shù),但前提是監(jiān)聽有序、管理冒泡。人性在開發(fā)中有溫暖協(xié)作的一面,也有冷酷泄漏的一面,自定義事件夾在中間,既真實(shí)表達(dá)需求又不過分失控。我愿稱其為JS事件的“恰到好處的加速器”,通過小挫敗(如傳播錯(cuò))促成成長(zhǎng),讓開發(fā)者越發(fā)堅(jiān)韌。
“不是組件在通信 —— 是事件在流動(dòng)。你的應(yīng)用沒有血液,再漂亮的UI也只是蠟像館。”
到此這篇關(guān)于從CustomEvent到dispatchEvent詳解JS中的自定義事件的文章就介紹到這了,更多相關(guān)JS自定義事件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
element plus表格的表頭和內(nèi)容居中的實(shí)現(xiàn)代碼
這篇文章主要介紹了element plus表格的表頭和內(nèi)容居中的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01
簡(jiǎn)易的JS計(jì)算器實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了JS簡(jiǎn)易的計(jì)算器實(shí)現(xiàn)代碼,,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
在Swiper內(nèi)如何制作CSS3動(dòng)畫效果示例代碼
這篇文章主要給大家介紹了關(guān)于在Swiper內(nèi)如何制作CSS3動(dòng)畫效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12
uniapp項(xiàng)目實(shí)踐之全局公共組件樣式及方法使用示例詳解
這篇文章主要為大家介紹了uniapp項(xiàng)目實(shí)踐之全局公共組件樣式及方法使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Summernote實(shí)現(xiàn)圖片上傳功能的簡(jiǎn)單方法
下面小編就為大家?guī)硪黄猄ummernote實(shí)現(xiàn)圖片上傳功能的簡(jiǎn)單方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-07-07

