咱們知道,在JavaScript中,原生DOM事件在開發中是頗有用的(與用戶交互的重要方式),可是操做原生DOM事件其實有兩大缺點:性能低、依賴於瀏覽器(NodeJs、小程序等不可用)。那麼這個時候,就須要咱們進行自定義事件去處理某些特定的業務。
在JavaScript中,全部事件的父對象就是Event對象,也就是說像咱們平時全部的點擊(click)、觸摸(touch)、鼠標等事件對象都繼承自Event。理解這一點是很重要的。先來簡單看一個事件的場景。javascript
場景1、頁面上有兩個按鈕a、b,當點擊按鈕b的時候,調用按鈕a的點擊事件。簡單佈局代碼以下:前端
<button id="btn1">a</button> <button id="btn2">b</button>
程序員A的作法,分別獲取這兩個按鈕,而後給b按鈕添加點擊事件後,調用按鈕a的click方法。代碼以下:vue
<button id="btn1" onclick="alert('I am a button named a')">a</button> <button id="btn2">b</button> <script> let btn1 = document.querySelector('#btn1'); let btn2 = document.querySelector('#btn2'); btn2.onclick = function(){ btn1.onclick(); } </script>
程序員B的作法,分別獲取這兩個按鈕,而後給b按鈕添加點擊事件後,在回調函數中在添加按鈕a的點擊事件。代碼以下:java
<button id="btn1">a</button> <button id="btn2">b</button> <script> let btn1 = document.querySelector('#btn1'); let btn2 = document.querySelector('#btn2'); btn2.onclick = function(){ btn1.addEventListener('click',function(){ alert('I am a button named a') },false) } </script>
看到這裏,你認爲誰的作法是正確的?顯然程序員A的作法是對的(就目前的要求來看),但有缺陷,若是按鈕a的事件是經過addEventListener方法去註冊監聽的,就不起做用了。那麼該怎樣作纔會比較好?這就須要咱們的Event對象和元素的dispatchEvent方法了。改進代碼以下:程序員
<button id="btn1">a</button> <button id="btn2">b</button> <script> let btn1 = document.querySelector('#btn1'); let btn2 = document.querySelector('#btn2'); btn1.addEventListener('click',function(){ alert('I am a button named a') },false) btn2.onclick = function(){ let ev = new Event('click'); btn1.dispatchEvent(ev); } </script>
其中:gulp
前面說過,在瀏覽器端javascript中,全部的事件都繼承自Event,那麼其實要想實現一個自定義事件,也是須要繼承自Event。小程序
仍是相似上面說過的場景,場景二:有兩個按鈕a,b,當調用b按鈕的點擊事件,怎麼去觸發a按鈕上的自定義事件?瀏覽器
<button id="btn1">a</button> <button id="btn2">b</button> <script> let a = document.querySelector('#btn1'); let b = document.querySelector('#btn2'); a.addEventListener('myClick',function(){ alert('I am a button named a') },false) class MyEvent extends Event{ constructor(...args){ super(...args); this.a = 12; } } b.onclick = function(){ const ev = new MyEvent('myClick'); a.dispatchEvent(ev); } </script>
這就是自定義事件的思想體現:函數
能夠看出,對事件進行管理是頗有必要,如Java中的EventBus、VueJs中的$on、$emit等,將事件的監聽者及分發者抽象成一個獨立的模塊,來進行事件的管理(添加、移除等)有利用解耦。佈局
這裏能夠把事件隊列想象成一根管道,相似前端gulp實現的核心思想(基於管道)、當使用者須要使用某個事件的時候,就在管道中註冊一個事件,而後經過該事件的類型,從管道中分發一個該類型的事件,在不須要使用後,還能夠對相應的事件進行移除操做。代碼以下:
class Pipe{ constructor(){ this.pipes = {}; } /** * 註冊事件 * @param {*} type * @param {*} fn */ on(type,fn){ this.pipes[type] = this.pipes[type] || []; if(this.pipes[type].findIndex(func => func==fn)==-1){ this.pipes[type].push(fn); } } /** * 移除事件 * @param {*} type * @param {*} fn */ off(type,fn){ if(this.pipes[type]){ this.pipes[type] = this.pipes[type].filter((func) => func!==fn); if(this.pipes[type].length===0){ delete this.pipes[type]; } } } /** * 分發事件 * @param {*} type * @param {...any} args */ emit(type,...args){ if(this.pipes[type]){ this.pipes[type].forEach((fn) => { fn(...args); }) } } }
場景:經過事件隊列模擬vuejs中組件間通訊的實現。Component1負責對數據進行渲染,Component2中點擊按鈕,來改變Component1實例屬性a的值。代碼以下:
<div id="box"> {{a}} </div> <button id="btn1">++</button> <script> const $ = document.querySelectorAll.bind(document); let pipe = new Pipe(); class Component1{ constructor(){ this.a = 12; this.el = $("#box")[0]; this.render(); pipe.on('add',(arg) => { this.a+=arg; this.render(); }) } render(){ this.el.innerHTML = this.a; } } class Component2{ constructor(){ this.el = $("#btn1")[0]; this.el.onclick = function(){ pipe.emit('add',12); } } } new Component1(); new Component2(); </script>