JavaScript事件代理入門

事件代理(Event Delegation),又稱之爲事件委託。是 JavaScript 中經常使用綁定事件的經常使用技巧。javascript

顧名思義,「事件代理」便是把本來須要綁定的事件委託給父元素,讓父元素擔當事件監聽的職務。html

 

爲何要這樣作呢?java

衆所周知,DOM操做是十分消耗性能的。因此重複的事件綁定簡直是性能殺手。而事件代理的核心思想,就是經過儘可能少的綁定,去監聽儘可能多的事件。瀏覽器

 

下面將會用 Zepto 爲你們演示怎麼實現事件代理。app

啊?Zepto是什麼?函數

Zepto is a minimalist JavaScript library for modern browsers with a largely jQuery-compatible API. If you use jQuery, you already know how to use Zepto.性能

因爲API是兼容 jQuery 的,熟悉jQuery的童鞋使用 Zepto 幾乎無需學習成本。演示代碼實際上也能在 jQuery 上正常運行。而目前 Zepto 的適用場景更可能是在移動端,一個遠離舊版IE的世界。學習

 

那爲何不直接用jQuery?this

由於下文會簡要分析源碼,但jQuery裏面爲了兼容舊版IE作了不少妥協,源碼十分不直觀。而 Zepto 是面向現代瀏覽器設計的,所用到的API絕大多數都符合W3C標準,分析起來更加直觀。spa

 

以這個HTML結構爲例:

<ul class="list">
    <li class="list_items">000</li>
    <li class="list_items">111 <a href="javascript:void(0);">link</a></li>
    <li class="list_items">222 <i>italic</i></li>
    <li>333</li>
</ul>

 

通常狀況下,會這樣綁定事件:

$('.list_items').on('click', function (e) {
    console.log(e.target.tagName);
    console.log(this.tagName);
});

Deom>>

 

打開瀏覽器的 Console 看一下,會發現每個 .list_items 元素都被綁定了click事件,而且綁定的對象是 li.list_items 。以下圖:

這意味着,Zepto的其實是遍歷了全部 .list_items 元素,並逐個綁定 click 事件。

實現思路和下面這段原生 JavaScript 代碼相同:

[].forEach.call(document.querySelectorAll('.list_items'), function (elem) {
  elem.addEventListener('click', function (e) {
    console.log(e.target.tagName);
    console.log(this.tagName);
  }, false);
});

這樣的作法,當遇到數量超長的列表(ul)和表格(table)時性能開銷很是大。

例如:ul 中有1000個 li 時,就須要進行1000次的事件綁定。

而事件代理,就是應用於這種場景的。

 

咱們先看一下Zepto官方的API文檔:

 

on 方法還支持在 回調函數 前傳入一個 [selector] 的值,而這個 [selector] 就是實際須要監聽事件的的元素。

看看實際例子:

$('.list').on('click', '.list_items', function (e) {
    console.log(e.target.tagName);
    console.log(this.tagName);
});

Demo>>

經過 on 方法把 click 事件代理在 ul.list 元素上,監聽其全部 .list_items 的子元素的點擊操做。

打開瀏覽器的 Console 看看:

雖然提示每一個 .list_items 都有事件監聽,但它們的綁定對象都是指向 ul.list 。

 

事件代理是經過什麼機制實現的? 

這其中的核心思想是 事件冒泡(Event Bubble) 。但在這裏我不打算細說事件冒泡,由於這會是一個很大的話題。Google一下,就能找到有不少相關的介紹了。

 

下面咱們看一下 Zepto 的源碼,它是怎麼去處理這個事件代理的。

方法 on 的實現:

  $.fn.on = function(event, selector, data, callback, one){
    var autoRemove, delegator, $this = this
    if (event && !isString(event)) {
      $.each(event, function(type, fn){
        $this.on(type, selector, data, fn, one)
      })
      return $this
    }

    if (!isString(selector) && !isFunction(callback) && callback !== false)
      callback = data, data = selector, selector = undefined
    if (isFunction(data) || data === false)
      callback = data, data = undefined

    if (callback === false) callback = returnFalse

    return $this.each(function(_, element){
      if (one) autoRemove = function(e){
        remove(element, e.type, callback)
        return callback.apply(this, arguments)
      }

      if (selector) delegator = function(e){
        var evt, match = $(e.target).closest(selector, element).get(0)
        if (match && match !== element) {
          evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
          return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
        }
      }

      add(element, event, callback, data, selector, delegator || autoRemove)
    })
  }

 

代碼的大體執行流程以下:

 

由上面的活動圖能夠看出,大部分邏輯都是用來處理 on 方法傳入參數,delegator 函數是事件代理的關鍵。

至於 add 方法,主要是對某些特殊事件進行處理,例如:ready、hover等。當成黑盒去看待,它就等同於原生的 addEventListener 方法。

下面再細分一下 delegator 函數的實現邏輯:

到這裏,你們應該都清楚了。

調用 on 方法進行事件綁定時,只有傳入 [selector] 參數纔會實現事件代理。

實際應用中,如何用 JavaScript 原生代碼寫出事件綁定呢?以下:

var listElem = document.querySelector('.list');
listElem.addEventListener('click', function (e) {
    var delegateTarget = this,
        fireTarget = e.target,
        eventTarget;

    if (fireTarget.className !== 'list_items') {
        function findParent(elem) {
            if (elem === delegateTarget) {
                return null;
            }
            var parent = elem.parentNode;
            if (parent.className === 'list') {
                return null;
            }
            if (parent.className === 'list_items') {
                return parent;
            }
            findParent(parent);
        }
        eventTarget = findParent(fireTarget);
    } else {
        eventTarget = fireTarget;
    }
    if (!eventTarget) return false;
    console.log('fireTarget: ' + fireTarget.tagName);
    console.log('eventTarget: ' + eventTarget.tagName);
}, false);

Demo>>

 

以上。

 

 

參考資料:

http://zeptojs.com/

 

 

本文做者:Maple Jan

本文連接:http://www.cnblogs.com/maplejan/p/3761519.html

相關文章
相關標籤/搜索