事件委託,通俗來講就是將元素的事件委託給它的父級或者更外級元素處理。javascript
事件流描述的是從頁面中接收事件的順序。
事件冒泡:事件開始由最具體的元素接收,而後逐級向上傳播到較爲不具體的節點(或文檔)。java
事件捕獲:事件開始由不太具體的節點接收,而後逐級向下傳播到最具體的節點。它與事件冒泡是個相反的過程。node
DOM2
級事件規定的事件流包括三個階段:jquery
事件委託就是利用事件冒泡機制實現的。
假設有一個列表,要求點擊列表項彈出對應字段。git
<ul id="myLink"> <li id="1">aaa</li> <li id="2">bbb</li> <li id="3">ccc</li> </ul>
不使用事件委託github
var myLink = document.getElementById('myLink'); var li = myLink.getElementsByTagName('li'); for(var i = 0; i < li.length; i++) { li[i].onclick = function(e) { var e = event || window.event; var target = e.target || e.srcElement; alert(e.target.id + ':' + e.target.innerText); }; }
這樣作存在的問題:web
使用事件委託瀏覽器
var myLink = document.getElementById('myLink'); myLink.onclick = function(e) { var e = event || window.event; var target = e.target || e.srcElement; if(e.target.nodeName.toLowerCase() == 'li') { alert(e.target.id + ':' + e.target.innerText); } };
上述代碼是將事件委託給列表項的父級,經過 target
下的 nodeName
屬性做出判斷。函數
也能夠給每一個列表項綁定與其對應的事件。如:性能
var myLink = document.getElementById('myLink'); myLink.onclick = function(e) { var e = event || window.event; var target = e.target || e.srcElement; switch(target.id) { case '1': target.style.backgroundColor = 'red'; break; case '2': alert('這是第二項'); break; case '3': alert(e.target.id + ':' + e.target.innerText); break; default: alert('...'); } };
上述代碼是經過判斷 target
下的 id
屬性,執行不一樣的事件。
事件委託的優勢:
須要注意的地方:
最適合採用事件委託技術的事件包括 click
、mousedown
、mouseup
、keydown
、keyup
和 keypress
。雖然 mouseover
和 mouseout
事件也冒泡,但要適當處理它們並不容易,並且常常須要計算元素的位置。(由於當鼠標從一個元素移到其子節點時,或者當鼠標移出該元素時,都會觸發 mouseout
事件。)
jquery中實現事件委託的幾種方法:
on(events, [selector], [data], fn)
// 將 li 的事件委託給它的父元素 $('#myLink').on('click', 'li', function() { // todo... });
該方法在 jquery 1.7
版本已被廢棄。
delegate(selector, [type], [data], fn)
$('#myLink').delegate('li', 'click', function() { // todo... });
該方法在 jquery 3.0
版本已被廢棄。用 on()
代替。
在 jquery
中, delegate()
、live()
、 one()
、bind()
等最終都是依賴 on()
方法實現的。所以能夠直接使用 on()
替代其餘方法。
須要注意的地方:
Element.matches
、事件 event
對象this
指向parentNode
查找,不然循環結束。/** * [delegateEvent description] * @param {[type]} parentSelector 父元素 * @param {[type]} targetSelector 目標元素 * @param {[type]} events 事件 * @param {Function} fn 回調函數 * @return {[type]} null */ function delegateEvent(parentSelector, targetSelector, events, fn) { // 事件綁定兼容性處理 function addEvent(ele, type, handle) { if(ele.addEventListener) { ele.addEventListener(type, handle, false); } else if(ele.attachEvent){ ele.attachEvent('on' + type, handle); } else { ele['on' + type] = handle; } } // 若是元素被指定的選擇器字符串選擇,Element.matches() 方法返回 true; 不然返回 false。 // 對於不支持 Element.matches() 或 Element.matchesSelector(),但支持 document.querySelectorAll() 方法的瀏覽器,存在如下替代方案 if (!Element.prototype.matches) { Element.prototype.matches = Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector || function(s) { var matches = (this.document || this.ownerDocument).querySelectorAll(s), i = matches.length; while (--i >= 0 && matches.item(i) !== this) {} return i > -1; }; } // 事件處理邏輯 addEvent(parentSelector, events, function(e) { // 兼容性處理 var e = e || window.event; var t = e.target || e.srcElement; // currentTarget === parentSelector var currentTarget = e.currentTarget; // 遍歷並判斷是否爲目標元素,若是不是,則往元素的 parentNode 繼續查找 while(!t.matches(targetSelector)) { // 若是是目標元素則跳出循環 if(t === currentTarget) { t = null; break; } t = t.parentNode; } if(t) { // 將回調函數的 this 指向目標元素 fn.call(t, Array.prototype.slice.call(arguments)); } }); }
調用示例:
<ul id="myLink"> <li id="1" class="link"><a href="javascript:;"><span>aaa</span></a></li> <li id="2" class="link"><a href="javascript:;">bbb</a></li> <li id="3" class="link">ccc</li> </ul>
var myLink = document.querySelector('#myLink'); delegateEvent(myLink, 'li.link', 'click', function() { console.log(this, this.id + ':' + this.innerText); });
原文地址:https://github.com/daijingfeng/blog/issues/1
參考資料:
一、《JavaScript高級程序設計(第3版)》
二、Element.matches() API https://developer.mozilla.org...