咱們在面試前端的過程當中,常常會聽到面試官問這樣的問題:javascript
若是我有一個頁面,裏面1000個元素都要綁定click事件,請問你要怎麼作html
若是你回答逐個綁定那估計能夠直接回家了,面試官但願的答案是你來高談闊論事件委託,你應該能給出方法並寫出解決方案。
接下來,考官必定要問,這麼作的好處是什麼,或者你爲何用事件委託。前端
我認爲好處主要有兩個:java
根據http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/ 更專業的說法以下:node
這還沒完,面試官一般會接下來問,那麼,你綁定在父級上,頁面怎麼知道你點擊的是哪個?面試
我想這應該回答,利用了事件的冒泡機制。瀏覽器
等等,挺在這裏,雖然不只一篇文章闡述了事件委託是利用了冒泡機制,得益於冒泡機制,可是,怎麼得益的,怎麼利用的。less
因而好奇的我引伸出另一個複雜的議題,事件的綁定和執行機制。dom
不少文章裏介紹了這個機制,這篇文章很簡明詳盡:
http://www.quirksmode.org/js/events_order.html函數
歸納一下,就是因爲歷史緣由,瀏覽器對事件的處理有兩種模式,一種是先執行外面父級元素的事件(捕獲模式 capturing),一種先執行內部元素的事件(冒泡模式bubbling)。這個概念就相似圖層,一種是外面的在前面,另外一種是裏面在前面。
現代瀏覽器裏參照w3c規範,採用了這兩種方式並行的方式,簡單的來講就是先捕獲,再冒泡。(爲何,爲何,規定爲一種很差麼)
咱們都知道爲某個元素註冊事件,是經過addEventListener這個方法,那麼,我直接註冊的事件,他是屬於捕獲執行呢,仍是冒泡執行呢?仍是說我捕獲階段執行一次,冒泡階段再執行一次。難道說每次都執行兩次?
不對,一樣一次綁定的方法是執行一次,由於它要麼屬於捕獲階段,要麼屬於冒泡階段。這兩個階段就像通向公司的兩條路,你去公司是一條路,回來是另一條路,你的事件是路上的小賣鋪,它要麼在去的那條路上,要麼在回來的那條路上。固然你也能夠兩條路上都開一家小賣鋪,雖然說沒什麼意義,可是這樣事件觸發的時候確實執行了兩次,不過這也能證實事件執行的兩個階段。
先忘掉上面的理論,下面咱們來作個試驗,記住下面的html,咱們打算爲out同時綁定兩個事件,看看執行順序是怎麼樣的
<div id="out"> <div id='inner1'> Click </div> </div>
var a = document.querySelector('div#out') var b = document.querySelector('div#inner1') a.addEventListener('click',function(e){ alert(e.target+'事件A') }) a.addEventListener('click',function(e){ alert(e.target+'事件B') })
咱們能夠看到,爲同一個元素前後綁定兩個事件,執行的順序是從上到下的,把事件B提到A前面就會先執行B。
接下來咱們試一下經過addEventListener第三個參數指定事件綁定在哪條路上,false爲冒泡階段,true爲捕獲階段。
a.addEventListener('click',function(e){ alert(e.target+'外部元素在冒泡階段') },false) a.addEventListener('click',function(e){ alert(e.target+'外部元素在捕獲階段') },true)
這下咱們能夠看到,不論冒泡在前面仍是後面,都是先執行捕獲階段的那行代碼,佐證了先前說道的w3c規範下的先執行捕獲再執行冒泡的行爲。
不過在這裏的實驗中,我無心發現了一個有趣的現象,當你把html改爲沒有子元素,好比
<div id="out"> Click </div>
這時候就不遵循先捕獲再冒泡的原則了,看起來像是判斷節點沒有子元素,就不須要使用捕獲和冒泡的流程,只採用先來後到的順序。這其中的原理還望高手指教。
總結: 當一個頁面元素包含子元素節點的時候,他在處理在其身上的綁定事件的時候,採用先執行捕獲階段的事件,再執行冒泡階段的事件。而事件處於哪一個階段,是由addevnetlistener的第三個參數決定的。
咱們都知道,阻止冒泡是採用相似 stopPropagation()
的方法。可是請考慮這樣一個問題:
a.addEventListener('click',function(e){ alert(e.target+'外部元素在冒泡階段') },false) a.addEventListener('click',function(e){ alert(e.target+'外部元素在捕獲階段') },true) b.addEventListener('click',function(e){ e.stopPropagation() alert(e.target+'內部元素上的事件') })
這段代碼裏,我點擊b,事件觸發的順序是怎樣的?
答案是:外部元素捕獲 ---> 內部元素事件
由於捕獲是永遠優先執行的,內部元素因爲不存在子元素,因此只有一個階段,無所謂先執行後執行,因爲自身沒有冒泡事件,因此stoppropagation()
掠過自身,尋找父級的冒泡階段上的事件,一次查找,所有給阻止掉。
因此,想要點擊內部的時候無視外部事件,必定不要把外部的事件放在捕獲階段,就是說第三個參數不要設爲true。
咱們再把代碼搞更復雜一些:
<div id="out"> <div id="inner1"> <div id="inner2"> click </div> </div> </div>
var a = document.querySelector('div#out') var b = document.querySelector('div#inner1') var c = document.querySelector('div#inner2') a.addEventListener('click',function(e){ alert('a在冒泡階段') },false) a.addEventListener('click',function(e){ alert('a在捕獲階段') },true) b.addEventListener('click',function(e){ alert('b在冒泡階段') },false) b.addEventListener('click',function(e){ alert('b在捕獲階段') },true) c.addEventListener('click',function(e){ alert(e.target+'內部元素事件') })
執行一遍,能夠加深了對事件這個模式的理解,順序是這樣的
a捕獲 ---> b捕獲 ---> 內部事件 ---> b冒泡 ---> a冒泡
這裏我感興趣的是阻止冒泡會怎麼樣,測下來是,若是把stoppropagation()
放在b,b自己的冒泡仍是會執行,那麼同理若是放在c,c自己若是有冒泡事件也會執行,
因此stoppropagation()
所作的事情能夠這麼理解,阻止父級元素冒泡階段的事件。
接下來我想引出本文的重點:事件委託和冒泡機制有關係嗎?
我認爲就算有關係,關係也不大。
咱們先來看一下一個常見的事件委託例子:
// Get the element, add a click listener... document.getElementById("parent-list").addEventListener("click",function(e) { // e.target is the clicked element! // If it was a list item if(e.target && e.target.nodeName == "LI") { // List item found! Output the ID! console.log("List item ",e.target.id.replace("post-")," was clicked!"); } });
簡言之,綁定在父類上一個事件,而後經過回調函數的參數得到當前點擊的是哪個元素,至關於把事件綁定在子元素身上。
請問這跟上文長篇累牘的冒泡機制有什麼聯繫?
假設不存在冒泡或者捕獲,在父類上點擊到了子類或者不論點到哪,這個事件都是要執行的,子類這個元素還會做爲引用傳到函數體裏,我實在看不出這個冒泡有什麼關係,若是要什麼致使事件委託能夠實現,應當是函數體內的引用纔是。
那麼,這個引用是什麼狀況呢,繼續上面的實驗:
var a = document.querySelector('div#out') var b = document.querySelector('div#inner1') var c = document.querySelector('div#inner2') a.addEventListener('click',function(e){ alert('a在冒泡階段') console.log(e.target)//inner2 console.log(this)//out console.log(e.currentTarget)//out },false) a.addEventListener('click',function(e){ alert('a在捕獲階段') console.log(e.target)//inner2 },true) b.addEventListener('click',function(e){ alert('b在冒泡階段') console.log(e.target)//inner2 },false) b.addEventListener('click',function(e){ alert('b在捕獲階段') console.log(e.target)//inner2 },true) c.addEventListener('click',function(e){ alert(e.target+'內部元素事件') console.log(e.target)//inner2 })
咱們能看到,不論在哪一層裏,e.target都是你當前點擊的自己,這絕不奇怪,由於e自己是一個event對象,好比這裏的MouseEvent,裏面還帶了是否同時按下alt鍵,鼠標位置等信息,可見這個對象自己能夠說是和綁定主體無關了,和事件有關。因此,和冒泡仍是沒啥關係。以上代碼裏還展現了兩種獲取事件執行主體的方法,分別是e.currentTarge 和 this
因此個人觀點是,雖然提到js的事件委託一般都會聯繫到冒泡,可是就算當初沒有設計冒泡和捕獲,事件委託仍是事件委託,它依賴的是event對象傳遞到監聽函數裏面了,和其餘無關。