事件委託和冒泡機制有關係嗎?

面試官提出的問題

咱們在面試前端的過程當中,常常會聽到面試官問這樣的問題:javascript

若是我有一個頁面,裏面1000個元素都要綁定click事件,請問你要怎麼作html

若是你回答逐個綁定那估計能夠直接回家了,面試官但願的答案是你來高談闊論事件委託,你應該能給出方法並寫出解決方案。
接下來,考官必定要問,這麼作的好處是什麼,或者你爲何用事件委託。前端

我認爲好處主要有兩個:java

  • 事件只須要綁定一次,而不是綁定1000次,提升效率
  • 動態添加進父級的元素自動擁有事件

根據http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/ 更專業的說法以下:node

  • Fewer functions to manage. 管理較少的函數
  • Takes up less memory. 更少的內存消耗
  • Fewer ties between your code and the DOM.下降代碼和dom之間的關聯
  • Don’t need to worry about removing event handlers when changing the DOM via修改dom的時候不用考慮刪除事件。

這還沒完,面試官一般會接下來問,那麼,你綁定在父級上,頁面怎麼知道你點擊的是哪個?面試

我想這應該回答,利用了事件的冒泡機制。瀏覽器

等等,挺在這裏,雖然不只一篇文章闡述了事件委託是利用了冒泡機制,得益於冒泡機制,可是,怎麼得益的,怎麼利用的。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對象傳遞到監聽函數裏面了,和其餘無關。

相關文章
相關標籤/搜索