通過釋出訂閱模式實現的事件委託

九酒發表於2018-11-24

關於這篇文章的背景

之前瞭解到的事件代理不多,就像是一個dom將事件委託給另一個dom,又叫事件委託。後來做了個題目,要實現一個類似jquery的事件委託方法,然後認真的瞭解了一下。然後專注於實現,其實並沒有去看jquery的原始碼,hhh。

釋出訂閱模式大概是目前前端框架使用的一種最常見的設計模式了,而我目前也只對釋出訂閱模式有一定了解,其他的設計模式待後續學習整理。

關於事件委託實現

在jquery中,實現事件委託只需要下面這一行程式碼就可以搞定

$("#container").on('click', 'item', fn)
複製程式碼

之前會覺得這麼用理所當然,所以並沒有想到哪一天我會親手去實現一個這樣的功能,現在機會來了。

題目給的很簡單,就是將子節點的事件綁到其父節點上,比如下面這段dom,就是將<li>的事件繫結到<ul>上,實現點選<li>時觸發<ul>上的事件。

<div  id="container">
    <li class="item" id="item1">
        <span id="btn1">hello</span>
    </li>
    <li class="item">
        <span>world</span>
    </li>
</div>
複製程式碼

題目的js部分是這樣的

!function(root, doc){
    class Delegator {
        constructor (selector/* root選擇器 */) {
        // TODO
        }

        on (event/* 繫結事件 */, selector/* 觸發事件節點對應選擇器 */, fn/* 觸發函式 */) {
          // TODO
        }

        destroy () {
        // TODO
        }
    }
}(window,document)
複製程式碼

然後實現一個功能類似上面jquery的事件委託,就像下面這樣的

var delegator = new Delegator('#container');
delegator.on('click', 'li.item', fnli)
複製程式碼

忘記一開始想的是什麼方法了,反正寫到一半的時候突然想起了訂閱釋出模式(因為剛好那幾天在看釋出訂閱模式),然後開始擼程式碼。

分解上面的方法,其實可以看作是一個監聽器

delegator.addEventListener('click', delegatorFn)
複製程式碼

只不過這個delegatorFn有點特殊,它需要遍歷這個所有委託‘click’事件給delegator的子節點,並執行他們的委託fnli

首先,我們把所有的委託當作是一個訂閱事件,只不過這個事件裡包含了委託者。在Delegator物件的原型裡增加一個屬性eventObj,裡面存放訂閱‘click’事件的所有的委託者和委託事件,結構差不多是這樣的

this.eventObj.click=[{
    selecter:'li.item',
    callback:fnli
}]
複製程式碼

那麼整個on的方法其實就是委託者selector訂閱事件並委託給調on的被委託者

on (event/* 繫結事件 */, selector/* 觸發事件節點對應選擇器 */, fn/* 觸發函式 */) {
      // TODO
    
      if(!this.eventsObj[event]){
        this.eventsObj[event] = [];
      }
      this.eventsObj[event].push({
          selector,
          callback: fn
      })
      //這裡委託事件給this.root,當被委託者堅挺到event事件時,會觸發委託函式delegatorFn
      this.root.addEventListener(event, delegatorFn);
      //因為on可以鏈式呼叫,所以這裡需要返回
      return this;
    }
複製程式碼

現在需要實現delegatorFn方法了,其實也很簡單,主要是遍歷事件經過的所有dom中哪個selector訂閱了它,它就執行對應節點攜帶的fn

delegatorFn = (e) => {
    let target = e.target;//觸發事件的selector
    let currentTarget = e.currentTarget; //被委託者
        //判斷觸發事件的節點及它冒泡經過的節點是否是被委託者,若是,則表示不再有委託者,無需遍歷
    while(target !== currentTarget){
        this.eventsObj[e.type].forEach(item => {
            //查詢訂閱事件者
            if(target.matches(item.selector)){
                //執行訂閱事件者攜帶的函式
                item.callback.call(target,e);
            }
        });
        //往上冒泡
        target = target.parentNode;
    }
}
複製程式碼

以上,就是通過釋出訂閱實現的事件委託的核心部分,主要涉及的還有事件的冒泡。

總結

主要涉及知識點:

  • 鏈式呼叫:在on方法裡返回this
  • 事件冒泡:對e.target進行往上查詢並執行觸發事件的回撥
  • 事件訂閱

相關文章