js 事件委託 事件代理
JavaScript高階程式設計上解釋:事件委託就是利用事件冒泡,只指定一個事件處理程式,就可以管理某一型別的所有事件。
通過例子類比:
有三個同事預計會在週一收到快遞。為簽收快遞,有兩種辦法:一是三個人在公司門口等快遞;二是委託給前臺MM為簽收。現實中,我們大都採用委託的方案。前臺MM收到快遞後,會判斷收件人是誰,然後按照收件人的要求籤收。這種方案的優勢就是無論有多少個新員工要收快遞,前臺MM都可以代為簽收。
這裡有兩層意思:
第一,現在委託前臺的同事是可以代為簽收的,即程式中的現有的DOM節點是有事件的;
第二,新員工也是可以被前臺MM代為簽收的,即程式中新新增的DOM節點也是有事件的。
為什麼要有事件委託:
如果很多個DOM需要新增事件處理,比如100個li都有相同的click點選事件,可能我們會用for迴圈的方法,來遍歷所有的li,然後給它們新增事件。在JavaScript中,新增到頁面上的事件處理程式數量將直接關係到頁面的整體執行效能,因為需要不斷的與DOM節點進行互動,訪問DOM的次數越多,引起瀏覽器重繪與重排的次數也就越多,就會延長整個頁面的互動就緒時間,這就是效能優化要減少DOM操作的原因:如果用事件委託,就會將所有的操作放到js程式裡,與DOM的操作就只需互動一次,這樣就能減少與DOM的互動次數,提高效能;
每個函式都是一個物件,是物件就會佔用記憶體,物件越多,記憶體佔用率就越大,效能也就會相應下降。比如上面的100個li,就要佔用100個記憶體空間,如果是1000,10000個,效能影響更加明顯。如果採用事件委託,就只需對它的父級(如果只有一個父級)這一個物件進行操作,我們就只需要一個記憶體空間就夠了,效能也會相應的提高。
事件委託原理:
事件委託是利用事件的冒泡原理來實現的,事件冒泡就是事件從最深的節點開始,然後逐級向上傳播事件。舉個例子:頁面上有一個節點樹,div>ul>li>a;比如給最裡面的a加一個click點選事件,那麼這個事件愛你就會逐層向外執行,執行順序為a>li>ul>div,事件委託的機制就在於我們可以給最外面的div加點選事件,那麼裡面的ul,li,a做點選事件的時候,都會冒泡到最外層的div上,委託它們的父級代為執行事件。
事件委託怎麼實現:
首先看一個例子:
子節點實現相同的功能:
<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); } } }
上面程式碼首先找到ul,然後遍歷li,然後點選li的時候,又要找一次目標的li的位置,才能執行最後的操作,每次點選都要找一次li;
如果用事件委託的方式做呢
window.onload = function(){ var oUl = document.getElementById("ul1"); oUl.onclick = function(){ alert(123); } }
這裡用父級ul做事件處理,當li被點選時,由於冒泡原理,事件就會冒泡到ul上,因為ul上有點選事件,所以事件就會觸發,但是這樣點選ul也會觸發,解決方案:
Event物件提供了一個屬性叫target,可以返回事件的目標節點,我們稱為事件源。即target可以表示為當前的事件操作的DOM。相容性問題:標準瀏覽器用ev.target,IE瀏覽器用event.srcElement,此時只是獲取了當前節點的位置,可以用nodeName來獲取具體是什麼標籤名,這裡返回是大寫的,需要轉成小寫的做比較:
window.onload = function(){ var oUl = document.getElementById("ul1"); oUl.onclick = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ alert(123); alert(target.innerHTML); } } }
這樣就只有點選li會觸發事件,且每次只執行一次DOM操作,如果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('選擇'); } }
上面程式碼4個按鈕,點選每一個做不同的操作,至少需要4次DOM操作,用事件委託進行優化:
window.onload = function(){ var oBox = document.getElementById("box"); oBox.onclick = function (ev) { var ev = ev || window.event; var target = ev.target || ev.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; } } } }
用事件委託就只用一次dom操作就能完成操作,提高效能
上面講的都是document載入完成的現有DOM節點下的操作,如果是新增節點的會有事件嗎?
<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 oBtn = document.getElementById("btn"); var oUl = document.getElementById("ul1"); var aLi = oUl.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'; } } //新增新節點 oBtn.onclick = function(){ num++; var oLi = document.createElement('li'); oLi.innerHTML = 111*num; oUl.appendChild(oLi); }; }
執行後發現,新增的li是沒有事件的,說明在新增子節點的時候,事件沒有一起新增進去。解決方案就是將for迴圈用一個函式包起來,命名mHover
window.onload = function(){ var oBtn = document.getElementById("btn"); var oUl = document.getElementById("ul1"); var aLi = oUl.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 (); //新增新節點 oBtn.onclick = function(){ num++; var oLi = document.createElement('li'); oLi.innerHTML = 111*num; oUl.appendChild(oLi); mHover (); }; }
雖然功能實現了,但實際上有增加了一個DOM操作,不利於效能,用事件委託優化
window.onload = function(){ var oBtn = document.getElementById("btn"); var oUl = document.getElementById("ul1"); var aLi = oUl.getElementsByTagName('li'); var num = 4; //事件委託,新增的子元素也有事件 oUl.onmouseover = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ target.style.background = "red"; } }; oUl.onmouseout = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ target.style.background = "#fff"; } }; //新增新節點 oBtn.onclick = function(){ num++; var oLi = document.createElement('li'); oLi.innerHTML = 111*num; oUl.appendChild(oLi); }; }
上面就是利用事件委託的方式,新新增的子元素是帶有事件效果的。當用事件委託的時候,不需要去遍歷元素的子節點,只需要給父級元素新增事件就可以了,其他的都是在js裡面執行,這樣就可以減少DOM操作,提高效能,這也是事件委託的優點。
適用事件委託的事件:click,mousedown,mouseup,keydown,keyup,keypress
(mouseover事件:不論滑鼠指標穿過被選元素或其子元素,都會觸發mouseover事件;mouseenter事件:只有滑鼠指標穿過被選元素時,才會觸發mouseenter事件。)