利用JavaScript自定義事件完成組件間的數據通信

我們知道,在JavaScript中,原生DOM事件在開發中是很有用的(與用戶交互的重要方式),但是操作原生DOM事件其實有兩大缺點:性能低、依賴於瀏覽器(NodeJs、小程序等不可用)。那麼這個時候,就需要我們進行自定義事件去處理某些特定的業務。

認識Event對象及元素的dispatchEvent方法

在JavaScript中,所有事件的父對象就是Event對象,也就是說像我們平時所有的點擊(click)、觸摸(touch)、鼠標等事件對象都繼承自Event。理解這一點是很重要的。先來簡單看一個事件的場景。

場景一、頁面上有兩個按鈕a、b,當點擊按鈕b的時候,調用按鈕a的點擊事件。簡單佈局代碼如下:

<button id="btn1">a</button>
<button id="btn2">b</button>

程序員A的做法,分別獲取這兩個按鈕,然後給b按鈕添加點擊事件後,調用按鈕a的click方法。代碼如下:

<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的點擊事件。代碼如下:

<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>

其中:

  • Event對象的構造函數需要一個參數,事件類型
  • dispatchEvent方法是將某個事件分發給某個元素

認識自定義事件

前面說過,在瀏覽器端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>







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