1. 程式人生 > >自定義瀏覽器事件,模擬瀏覽器事件流

自定義瀏覽器事件,模擬瀏覽器事件流

簡述

我認為,事件是我們前端最為熟悉的程式設計模式,在前端開發中能接觸太多太多,而且相對而言,事件是一種相對容易理解,邏輯性高的的模式,對於優化元件/外掛的邏輯是一個很好的應用模式。

這文章主要是用JavaScript實現3級dom事件機制,後面的更新會涉及應用倒元件開發當中。

  1. 瀏覽器事件一:事件的基礎應用
  2. 瀏覽器事件二:瀏覽器是如何實現事件
  3. 瀏覽器事件三:自定義事件

如何實現

jQuery 實現方式

jQuery 是實現了一個獨立的事件方法,甚至可以在自定義物件(object)上繫結事件,功能十分強大。
例:

//import jQuery
$(selector).on('yourEvent',function(){
    console.log('i m yourEvent handler');
})

$(selector).trigger('yourEvent')

上面例子就是jQuery 的自定義事件實現方式,下面再展示一個繫結物件上的事件,它其實是一個訂閱/釋出模式的實現 —> cowboy 實現的簡易訂閱/釋出
例:

(function($) {

  var o = $({});

  $.subscribe = function() {
    o.on.apply(o, arguments);
  };

  $.unsubscribe = function() {
    o.off.apply(o, arguments);
  };

  $.publish = function() {
    o.trigger.apply(o, arguments);
  };

}(jQuery));

jQuery的事件實現原理這裡不作陳述,這裡提供Aaron的jQuery原始碼解析傳送門

jQuery實現的優點

  1. 良好的維護性
    jQuery是一個使用率最高的前端庫,這意味這它能提供非常友好的文件,以及學習社群。從專案的角度上來看這是提高維護性的一個做法。
  2. 良好的相容性
    解決相容性是意見相當麻煩瑣碎的事情,如果沒有充足的經驗,很難再編碼的的開始就預計到那些相容問題,所以jQuery 本身某種層度就是提供相容處理封裝。

jQuery實現的缺點

  1. 依賴jQuery
    實現上,我們並不是所有專案都需要依賴jQuery,jQuery本身雖然十分好,但是jq鏈式操作dom寫出來的頁面/元件,再可讀性上確實不盡人意,業界都是認同那是一坨義大利麵(特別是webapp專案
    )。所以其實從另一個層面上看,jQuery並不是一個十分好提高維護性的方案。
  2. 能實現的功能有限
    如果單純做元件抽象,或者基於監聽響應實現的物件,jq的實現確實是滿足並且能提供簡潔的實現方法,但是在某些需求上不一定能滿足,如:如果我們希望元件的實現邏輯更加清晰,希望加入一個事件響應流,能在元件之間鏈式傳播。
  3. 效能上
    jQuery事件實現內部也是已經實現事件的,如果在jq之上在新增自定義的事件功能,效能上必然有影響(不可控),效能影響不會很大,但做效能優化時會是一個問題。

任意物件繫結事件

在原生JavaScript物件,自定義的object 物件並沒有事件的概念, 而實際上 我們希望某些元件擁有事件驅動,js擁有完整的事件驅動物件,但是我們開發者沒有辦法使用(第二章有提及),不過我們還是能基於官方的文件事件一個與js一樣的事件物件。

官方自定義 eventtarget

我們從官方的事件文件中看到一個簡單的事件驅動物件實現例子。

var eventTarget = function() {
  this.listeners = {};
};

eventTarget.prototype.listeners = null;
eventTarget.prototype.addEventListener = function(type, callback) {
  if(!(type in this.listeners)) {
    this.listeners[type] = [];
  }
  this.listeners[type].push(callback);
};

eventTarget.prototype.removeEventListener = function(type, callback) {
  if(!(type in this.listeners)) {
    return;
  }
  var stack = this.listeners[type];
  for(var i = 0, l = stack.length; i < l; i++) {
    if(stack[i] === callback){
      stack.splice(i, 1);
      return this.removeEventListener(type, callback);
    }
  }
};

eventTarget.prototype.dispatchEvent = function(event) {
  if(!(event.type in this.listeners)) {
    return;
  }
  var stack = this.listeners[event.type];
  event.target = this;
  for(var i = 0, l = stack.length; i < l; i++) {
      stack[i].call(this, event);
  }
};

這個例子可以是我們實現自己事件的最頂層類。

如何繫結到自定義物件

假設我們已經有了 eventTarget物件,下面我們要做的就是把事件繫結到物件上

var component = function(){};

component.prototype = new eventTarget();

var com_1 = new component();
com_1.addEventListener('click',function(){
    console.log('i m listenning click event')
});

com_1.dispatchEvent(new MouseEvent('click')) //i m listenning click event

加入htmlElement事件流

htmlElement的事件流是獨立的,實際上的C++在做事件流的傳遞,自定義的事件沒法加入到這個事件流當中。
這種冒泡的事件流機制非常常見,在ios,Android中的事件驅動都是類似這種機制。在事件中這樣的思想/機制能把事件的耦合度降低。在大型元件的事件邏輯中,加入這種機制個人認為是非常好的。

這裡提供一個實現思路,鄙人愚見,請大神指點。
這裡寫圖片描述

建立自己的物件的事件流

 var  flowEvent = function (parentNode) {
        this.listeners = {};
        this.childrenNode = [];
        this.parentNode = parentNode; // 父節點

        parentNode && parentNode.childrenNode.push(this) //加入父節點的子節點

}

flowEvent.prototype = new eventTarget(); //繼承事件

flowEvent.prototype.dispatchEvent = function (event) { //重寫事件分發方法
    if(!(event.type in this.listeners)) {
        return;
    }
    var stack = this.listeners[event.type]; //要獨立lisenter
    event.target = this;
    for(var i = 0, l = stack.length; i < l; i++) {
        stack[i].call(this, event);
    }
    if(event.target){
    //todo 加入阻止同類事件&阻止傳遞
        this.childrenNode.length>0 && this.childrenNode.forEach(function (v) {
            v.dispatchEvent(event)
        })
    }else{
        this.parentNode && this.parentNode.dispatchEvent(event)
    }
}