標籤:很多 child 延長 重繪 func lis ... i++ javascrip
JavaScript進階程式設計上講:事件委託就是利用事件冒泡,只指定一個事件處理常式,就可以管理某一類型的所有事件。使用事件委託技術能讓你避免對特定的每個節點添加事件監聽器;相反,事件監聽器是被添加到它們的父元素上。事件監聽器會分析從子項目冒泡上來的事件,找到是哪個子項目的事件。
為什麼要用事件委託:
一般來說,dom需要有事件處理常式,我們都會直接給它設事件處理常式就好了,那如果是很多的dom需要添加事件處理呢?比如我們有100個li,每個li都有相同的click點擊事件,可能我們會用for迴圈的方法,來遍曆所有的li,然後給它們添加事件,那這麼做會存在什麼影響呢?
在JavaScript中,添加到頁面上的事件處理常式數量將直接關係到頁面的整體運行效能,因為需要不斷的與dom節點進行互動,訪問dom的次數越多,引起瀏覽器重繪與重排的次數也就越多,就會延長整個頁面的互動就緒時間,這就是為什麼效能最佳化的主要思想之一就是減少DOM操作的原因;如果要用事件委託,就會將所有的操作放到js程式裡面,與dom的操作就只需要互動一次,這樣就能大大的減少與dom的互動次數,提高效能;
事件委託的原理:
事件委託是利用事件的冒泡原理來實現的,何為事件冒泡呢?就是事件從最深的節點開始,然後逐步向上傳播事件,舉個例子:頁面上有這麼一個節點樹,div>ul>li>a;比如給最裡面的a加一個click點擊事件,那麼這個事件就會一層一層的往外執行,執行順序a>li>ul>div,有這樣一個機制,那麼我們給最外面的div加點擊事件,那麼裡面的ul,li,a做點擊事件的時候,都會冒泡到最外層的div上,所以都會觸發,這就是事件委託,委託它們父級代為執行事件。
<ul id="parent-list"> <li id="post-1">Item 1</li> <li id="post-2">Item 2</li> <li id="post-3">Item 3</li> <li id="post-4">Item 4</li> <li id="post-5">Item 5</li> <li id="post-6">Item 6</li></ul>
當每個子項目被點擊時,將會有各自不同的事件發生。你可以給每個獨立的li元素添加事件監聽器,但有時這些li元素可能會被刪除,可能會有新增,監聽它們的新增或刪除事件將會是一場噩夢,尤其是當你的監聽事件的代碼放在應用的另一個地方時。當子項目的事件冒泡到父ul元素時,你可以檢查事件對象的target屬性,捕獲真正被點擊的節點元素的引用。簡單:下面是一段很簡單的JavaScript代碼,示範了事件委託的過程:
// 找到父元素,添加監聽器...document.getElementById("parent-list").addEventListener("click",function(e) { // e.target是被點擊的元素! // 如果被點擊的是li元素 if(e.target && e.target.nodeName == "LI") { // 找到目標,輸出ID! console.log("List item ",e.target.id.replace("post-")," was clicked!"); }});
看下面例子,用一般方法讓子節點實現相同功能
<ul id="ul1"> <li>111</li> <li>222</li> <li>333</li> <li>444</li> </ul>
實現功能是:點擊li彈出123
window.onload=function(){ var oUl = document.getElementById(‘ul1‘); var aLi = oUl.getElementsByTagName(‘li‘); for(var i = 0;i<aLi.length;i++){ aLi[i].onclick = function(){ alert(123) } } }
用事件委託方式:
window.onload=function(){ var oUl = document.getElementById(‘ul1‘); oUl.onclick = function(e){ var e = e || window.event; var target = e.target || e.srcElement; if(target.nodeName.toLowerCase() == ‘li‘){ alert(123); alert(target.innerHTML); } } }
這樣改下就只有點擊li會觸發事件了,且每次只執行一次dom操作,如果li數量很多的話,將大大減少dom的操作,最佳化的效能。
但是,上面是說li操作的同樣的效果,要是每個li被點擊的效果不一樣,那麼事件委託還有用嗎?
一般方法:需要4次dom操作
<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 oBox = document.getElementById(‘box‘); oBox.onclick= function(e){ var e = e || window.event var target = e.target || e.srcElement if(target.nodeName.toLocaleLowerCase() == ‘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="ul1"> <li>111</li> <li>222</li> <li>333</li> <li>444</li> </ul>
現在是移入li,li變紅,移出li,li變白,這麼一個效果,然後點擊按鈕,可以向ul中添加一個li子節點,用一般方法:
window.onload = function(){ var btn = document.getElementById(‘btn‘); var oUl = document.getElementById(‘ul1‘); var aLi = document.getElementsByTagName(‘li‘); var num = 4; for(var i = 0;i<aLi.length;i++){ aLi[i].onmouseover = function(){ this.style.background=‘red‘ } aLi[i].onmouseout = function(){ this.style.background=‘#fff‘ } } btn.onclick = function(){ num++; var oLi = document.createElement(‘li‘); oLi.innerHTML = 111*num; oUl.appendChild(oLi); } }
但是你會發現,新增的li是沒有事件的,說明添加子節點的時候,事件沒有一起添加進去,解決:將for迴圈用一個函數包起來,命名為mHover
window.onload = function(){ var btn = document.getElementById(‘btn‘); var oUl = document.getElementById(‘ul1‘); var aLi = document.getElementsByTagName(‘li‘); var num = 4; function mHover(){ for(var i = 0;i<aLi.length;i++){ aLi[i].onmouseover = function(){ this.style.background=‘red‘ } aLi[i].onmouseout = function(){ this.style.background=‘#fff‘ } } } mHover(); btn.onclick = function(){ num++; var oLi = document.createElement(‘li‘); oLi.innerHTML = 111*num; oUl.appendChild(oLi); mHover(); } }
雖然功能實現了,看著還挺好,但實際上無疑是又增加了一個dom操作,在最佳化效能方面是不可取的,用事件委託可以最佳化:
window.onload = function(){ var btn = document.getElementById(‘btn‘); var oUl = document.getElementById(‘ul1‘); var aLi = document.getElementsByTagName(‘li‘); var num = 4; oUl.onmouseover = function(e){ var e = e || window.event var target = e.target || e.srcElement if(target.nodeName.toLocaleLowerCase()==‘li‘){ target.style.background = "red"; } } oUl.onmouseout = function(e){ var e = e || window.event var target = e.target || e.srcElement if(target.nodeName.toLocaleLowerCase()==‘li‘){ target.style.background = "#fff"; } } btn.onclick = function(){ num++; var oLi = document.createElement(‘li‘); oLi.innerHTML = 111*num; oUl.appendChild(oLi); } }
上面是用事件委託的方式,新添加的子項目是帶有事件效果的,我們可以發現,當用事件委託的時候,根本就不需要去遍曆元素的子節點,只需要給父級元素添加事件就好了,其他的都是在js裡面的執行,這樣可以大大的減少dom操作,這才是事件委託的精髓所在。
總結:
那什麼樣的事件可以用事件委託,什麼樣的事件不可以用呢?
適合用事件委託的事件:click,mousedown,mouseup,keydown,keyup,keypress。
值得注意的是,mouseover和mouseout雖然也有事件冒泡,但是處理它們的時候需要特別的注意,因為需要經常計算它們的位置,處理起來不太容易。
不適合的就有很多了,舉個例子,mousemove,每次都要計算它的位置,非常不好把控,在不如說focus,blur之類的,本身就沒用冒泡的特性,自然就不能用事件委託了。
JavaScript事件委託