js中的事件委託

概述:

        什麼叫事件委託?他的另外一個名字叫事件代理。《JavaScript高級程序設計》13.5章節對其進行了詳細描述:事件委託就是利用事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。javascript

        舉個栗子:取快遞。有三位同事預計會在今天收到各自的快遞,爲了簽收,有兩種方法:1.三我的站在門口等着,2.委託給前臺MM代收。現實中,咱們都會採用第二種方式,前臺MM收到快遞後,判斷是誰的快遞,是否須要到付,另外,即便是公司新來的員工,前臺MM也能夠代爲簽收。html

        這裏有兩層意思:java

        第一,你們能夠經過委託前臺MM代收快遞,即程序中現有DOM節點是有事件的;node

        第二,新員工也能夠經過前臺MM代收快遞,即程序中新添加的DOM節點也是有事件的。瀏覽器

爲何使用事件委託:

        通常來講,DOM須要有事件處理程序,咱們直接給他設置就行,可是若是不少DOM須要時間處理程序呢?假若有100個 li 須要添加 click 事件,咱們能夠用 for 循環遍歷全部 li 元素,給他們添加事件,可是這樣作好嗎?性能優化

        在 JavaScript 中,添加到頁面上的事件處理程序數量將會直接影響到頁面的總體運行性能,由於須要不斷與DOM節點進行交互,訪問DOM次數越多,引發的瀏覽器重繪與重排的次數也就越多,就會延長整個頁面的交互就緒時間,這也是爲何性能優化的主要思想之一就是要減小對DOM的操做。若是使用事件委託,則只須要一次DOM交互便可,這樣就能大大提高性能。app

        咱們都知道,每一個函數都是一個對象,都會佔用內存,對象越多,佔用內存就越大。若是使用事件委託,那麼咱們就能夠只對他的父級這一個對象進行操做,大大節省了內存,提高了性能。dom

事件委託的原理:

        首先要知道,事件委託是利用事件冒泡實現的,什麼是事件冒泡?《JavaScript高級程序設計》13.1章有詳細介紹。簡單說,事件是從DOM樹的最深節點開始,逐層向上傳播事件。舉個栗子:頁面上有這麼一個節點樹,div>ul>li>a,此時給 a 添加一個 click 事件,那麼就會一層一層往上執行,假如恰好最外層的 div 也有一個 click 事件,那麼 div 的事件也會被觸發,這就是事件冒泡。函數

事件委託的實現方法:

到重點部分了,咱們先來看一段通常方法的例子:性能

<ul id="ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
</ul>

實現功能是點擊 li ,彈出 hello world

window.onload = function(){
  var dom_ul = document.getElementById('ul');
  var dom_lis = document.getElementsByTagName('li');
  for(var i-0;i<dom_lis.kenght;i++){
    dom_lis[i].onclick = function(){
      alert('hello world');
    }
  }
}

上面的代碼很簡單,相信不少人都是這樣作的。分析一下,看看有多少次的DOM操做:首先找到 ul,而後遍歷 li ,點擊 li 的時候,又要找一次目標的 li 的位置,才能執行最後的操做。

使用事件委託又會是怎麼樣呢?

window.onload = function(){
    var dom_ul = document.getElementById("ul");
    dom_ul .onclick = function(){
        alert('hello world');
    }
}

這裏用父級元素 ul 作事件處理,當 li 被點擊時,因爲冒泡原理,事件會冒泡到 ul 上,由於 ul 上有點擊事件,因此事件會被觸發,固然了,這裏點擊 ul 的時候,也是會觸發的,那麼問題來了,若是咱們想讓事件代理的效果跟直接給節點的事件效果同樣怎麼辦,好比說只有點擊 li 時纔會觸發。

 Event 對象提供了一個屬性,叫作 target ,能夠返回事件的目標節點,也就是事件源,target 就能夠表示爲當前事件操做的DOM,可是並不須要去操做DOM。可是,這個有兼容性問題,標準瀏覽器採用 Event.target,IE瀏覽器採用 Event.srcElement。此時咱們獲取到了當前節點的位置,可是並不知道是什麼節點,咱們用 nodeName 來獲取名稱,返回值是大寫的,須要轉換爲小寫,以下:

window.onload = function(){
    var dom_ul = document.getElementById('ul');
    dom_ul.onclick = function(event){
        var event = event || window.event;
        var target = event.target || event.srcElement;
        if(target.nodeName.toLowerCase() == 'li'){
            alert('hello world');
        }
    }
}

這樣的話,就只有點擊 li 纔會觸發事件了,且每次只執行一次DOM操做,大大提高了性能。

 

上面的例子是在 li 的操做相同狀況下的,如果每一個 li 的點擊效果不一樣呢?

<div id="box">
    <input type="button" id="add" value="添加" />
    <input type="button" id="remove" value="刪除" />
    <input type="button" id="move" value="移動" />
    <input type="button" id="select" value="選擇" />
</div>
window.onload = function(){
    var Add = document.getElementById('add');
    var Remove = document.getElementById('remove');
    var Move = document.getElementById('move');
    var Select = document.getElementById('select');

    Add.onclick = function(){
        alert('添加');
    }
    Remove.onclick = function(){
        alert('刪除');
    }
    Move.onclick = function(){
        alert('移動');
    }
    Select.onclick = function(){
        alert('選擇');
    }
}

怎樣進行事件委託呢?

window.onload = function () {
    var Box = document.getElementById('box');
    Box.onclick = function (ev) {
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if (target.nodeName.toLowerCase() == 'input') {
            switch (target.id) {
                case 'add':
                    alert('添加');
                    break;
                case 'remove':
                    alert('刪除');
                    break;
                case 'move':
                    alert('移動');
                    break;
                case 'select':
                    alert('選擇');
                    break;
            }
        }
    }
}

 

新狀況又出現了,來了個新員工,他能收到快遞嗎?

正常狀況下添加節點元素:

<input type="button" name="" id="btn" value="添加" />
<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
</ul>

指望效果是:鼠標移入 li ,li 變紅,鼠標移出 li , li 變白,點擊「添加」按鈕能夠向 ul 中添加一個 li

window.onload = function () {
    var Btn = document.getElementById('btn');
    var ul = document.getElementById('ul');
    var lis = document.getElementsByTagName('li');
    var num = 4;
    //鼠標移入變紅,移出變白
    for (var i = 0; i < lis.length; i++) {
        lis[i].onmouseover = function(){
            this.style.background = 'red';
        };
        lis[i].onmouseout = function(){
            this.style.background = 'white';
        }
    }
    //添加新節點
    Btn.onclick = function(){
        num++;
        var li = document.createElement('li');
        li.innerHTML = num;
    }
}

這是通常的作法,你會發現新增的 li 是沒有事件的,使用事件委託能夠嗎?

答案固然是能夠,看看下面優化後的作法:

window.onload = function(){
    var Btn = document.getElementById('btn');
    var ul = document.getElementById('ul');
    var lis = document.getElementsByTagName('li');
    var num = 4;
    //事件委託,添加的子元素也有事件
    ul.onmouseover = function(ev){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase() == 'li'){
            target.style.background = 'red';
        }
    }
    ul.onmouseout = function(){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase() == 'li'){
            target.style.background = 'white';
        }
    }
    Btn.onclick = function(){
        num++;
        var li = document.createElement('li');
        li.innerHTML = num;
        ul.appendChild(li);
    }
}

採用事件委託的方式,咱們不須要去遍歷子元素,只須要給父元素添加事件便可,而且新生成的元素也是有事件的。這就是事件委託的精髓所在。

 

還有一種狀況,假如結構是這樣的 div>ul>li>a,不一樣點在於ul,li,a都是有寬高,而且和 div 是同樣大的,此時仍是給 ul 綁定事件,那麼 target 就有多是 li ,也有多是 a ,如何作區分呢?

var ul = document.getElementById("ul");
ul.addEventListener("click", function(ev) {
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    while (target.nodeName.toLowerCase() !== "ul") {
        if (target.nodeName.toLowerCase() == "li") {
            alert("hello world");
            break;
        }
        target = target.parentNode;
    }
});

核心代碼是 while 循環部分,其實是一個遞歸調用,也能夠攜程一個函數,用遞歸方法來調用,用冒泡原理,從裏往外冒,直到達到當前位置,當此時的 target 是 li 的時候,就能夠執行對應的事件了,而後終止循環。

 

總結:

適合使用事件委託的事件:click,mousedown,mouseup,keydown,keyup,keypress。

不適合的就不少了,mousemove,每次都要計算他的位置,很難把控,focus,blur之類的,自己就沒有冒泡屬性,天然也就不能用事件委託了。

相關文章
相關標籤/搜索