捕獲、冒泡與事件委托

學(xué)這個的時候,這東西把我搞得很頭暈。于是決定寫一篇Blog出來把這玩意說清楚,讓和我一樣在這個坑里打滾的人不那么暈菜。盡量照顧到每個細(xì)節(jié),標(biāo)準(zhǔn)是讓自己再看的時候不管忘記了多少知識點(diǎn)都能看得懂。

那么,開始吧。

基礎(chǔ)知識

1.鼠標(biāo)點(diǎn)擊。

我的鼠標(biāo)左鍵點(diǎn)擊了網(wǎng)頁,然后網(wǎng)頁獲取到了我的點(diǎn)擊事件,調(diào)取了DOMAPI——真的是這樣嗎?

不,不是這樣的。鼠標(biāo)首先應(yīng)該是系統(tǒng)級的API,所以最先知情的應(yīng)該是系統(tǒng)。系統(tǒng)得知你點(diǎn)擊了鼠標(biāo),并把這個事件傳遞給瀏覽器,然后瀏覽器才會通過DOMAPI通知網(wǎng)頁。這很重要。

2.事件綁定

DOM的事件元素綁定其實(shí)沒啥好說的,無非就是綁定的方法不同。一種是直接綁定,就是xx.onclick這樣的。簡單粗暴,DOM level0就支持。等等,什么是DOM level0?這又引出了一個隱藏知識點(diǎn)。DOM level0 指的是DOM level1事件之前即支持的事件,DOM level1時間基準(zhǔn)為W3C制定第一個標(biāo)準(zhǔn)。另外一種,就是DOM level2新增的事件監(jiān)聽,見下文。

3.child元素和parent元素的通知問題。
根據(jù)1,那么我點(diǎn)擊了child元素并且child元素被監(jiān)聽,那么就會出現(xiàn)一個通知順序的問題。是父元素先通知,還是子元素先通知?那么,就順理成章的進(jìn)入了下一個介紹:

捕獲和冒泡

要說捕獲和冒泡,得先介紹事件監(jiān)聽。事件監(jiān)聽和綁定其實(shí)差不了太多,但是新增了一個特點(diǎn),就是無論監(jiān)聽多少次,都不會覆蓋掉前面的事件。講白了就是事件綁定plus。而捕獲和冒泡其實(shí)本質(zhì)上只是Child和Parent通知的兩種順序。捕獲指的是parent先通知,child后通知,而冒泡則相反.

捕獲只有在早期的瀏覽器中才使用,第一個嘗試冒泡的,居然是現(xiàn)在被批判為封建保守的IE。有因有果,這都是有故事的啊~

一大堆概念解釋:

事件捕獲:當(dāng)某個元素觸發(fā)某個事件(如onclick),頂層對象document就會發(fā)出一個事件流,隨著DOM樹的節(jié)點(diǎn)向目標(biāo)元素節(jié)點(diǎn)流去,直到到達(dá)事件真正發(fā)生的目標(biāo)元素。在這個過程中,事件相應(yīng)的監(jiān)聽函數(shù)是不會被觸發(fā)的。

事件目標(biāo):當(dāng)?shù)竭_(dá)目標(biāo)元素之后,執(zhí)行目標(biāo)元素該事件相應(yīng)的處理函數(shù)。如果沒有綁定監(jiān)聽函數(shù),那就不執(zhí)行。

事件冒泡:從目標(biāo)元素開始,往頂層元素傳播。途中如果有節(jié)點(diǎn)綁定了相應(yīng)的事件處理函數(shù),這些函數(shù)都會被一次觸發(fā)。

有些情況下,捕獲/冒泡并不被我們所需要,比如調(diào)試。那么我們可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)來阻止。e是什么?DOM Event。當(dāng)然,我們也能通過改變addEventListener的第三個參數(shù)改變事件的執(zhí)行順序。(false為冒泡階段執(zhí)行,true為捕獲階段執(zhí)行,默認(rèn)為false)我想,大概永遠(yuǎn)都用不到True了吧....

事件委托(事件代理)

介紹完上面的,事件委托是時候登場了。事件委托簡單說起來就是利用事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件。
網(wǎng)上我找了很多篇博客,基本都是用這個例子,我就直接抄過來了:
有三個同事預(yù)計(jì)會在周一收到快遞。為簽收快遞,有兩種辦法:一是三個人在公司門口等快遞;二是委托給前臺MM代為簽收。現(xiàn)實(shí)當(dāng)中,我們大都采用委托的方案,前臺MM收到快遞后,她會判斷收件人是誰,然后按照收件人的要求簽收,甚至代為付款。這種方案還有一個優(yōu)勢,那就是即使公司里來了新員工(不管多少),前臺MM也會在收到寄給新員工的快遞后核實(shí)并代為簽收。

嗯,我大概明白是啥意思了。來個例子加深一下印象吧。

<ul id="ul1">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>

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);
        }
    }
}

這是一段傳統(tǒng)的的DOM操作遍歷li實(shí)現(xiàn)事件操作的代碼。有用,但是很蠢。遍歷每一個li?真的有這個必要嗎?
聰明的人已經(jīng)想到了,那么我監(jiān)聽ul不就好了嗎!bingo,那么用事件委托監(jiān)聽ul會怎么樣呢?

window.onload = function(){
    var oUl = document.getElementById("ul1");
   oUl.onclick = function(){
        alert(123);
    }       

嗯,點(diǎn)擊ul確實(shí)已經(jīng)實(shí)現(xiàn)了功能。但是出現(xiàn)了一個bug.當(dāng)我們點(diǎn)擊li之外,ul之內(nèi),事件一樣觸發(fā)了。因?yàn)槭录墙壎ㄔ趗l上的??!也許通過控制ul的樣式能夠解決這個問題,但是并不是一個好辦法。嗯,其實(shí)寫個判斷就能夠解決了。這里有個需要解釋的e.什么是e?DOM Event.它提供了一個屬性叫target,可以返回事件的目標(biāo)節(jié)點(diǎn)。有個小坑:webkit等瀏覽器一般是使用e.target,而IE瀏覽器要用event.srcElement.而且這個target只是獲取節(jié)點(diǎn)位置,我們還需要用nodeName獲取標(biāo)簽名,并轉(zhuǎn)化為小寫做比較。

        


ul.addEventListener('click', function(e) {
                    // 檢查事件源e.targe是否為Li
                    if (e.target && e.target.nodeName.toUpperCase == "LI") {

                        // 真正的處理過程在這里
                        console.log("123");
                    }
                }

這樣就只有點(diǎn)擊li會觸發(fā)事件了,并且只執(zhí)行一次dom操作。絕大多數(shù)的blog也是這么寫的。

但是,它有bug!

如果第一個li外面套了一個span呢?

<ul id="ul1">
    <span><li>111</li></span>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>

再次點(diǎn)擊第一個li,它居然不觸發(fā)!為什么?console大法一下,發(fā)現(xiàn)我們點(diǎn)擊的居然是span.那么這個循壞就錯的徹徹底底了。但是其實(shí)做個小小的修改就行了。

ul.addEventListener('click', function() {
                    let el = e.target
                    while (el && !el.matches(selector)) {
                        el = el.parentNode
                        if (element === el) {
                            el = null
                        }
                    }
                    if (el) {
                        console.log('123')
                    }
                }       


OK,寫寫抄抄終于總結(jié)完了,自己算是弄懂了,下次有機(jī)會就用起來試試~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 0.起因 前幾天寫業(yè)務(wù)的時候,碰到了這樣的需求: checkbox在父DIV的里面,但是checkbox綁定了v-...
    CoderMageFox閱讀 934評論 1 0
  • (續(xù)jQuery基礎(chǔ)(1)) 第5章 DOM節(jié)點(diǎn)的復(fù)制與替換 (1)DOM拷貝clone() 克隆節(jié)點(diǎn)是DOM的常...
    凜0_0閱讀 1,370評論 0 8
  • 總結(jié): 鼠標(biāo)事件 1.click與dbclick事件$ele.click()$ele.click(handler(...
    阿r阿r閱讀 1,640評論 2 10
  • 1.背景介紹 1.1什么是事件委托? 事件委托還有一個名字叫事件代理,JavaScript高級程序設(shè)計(jì)上講:事件委...
    我叫于搞吧閱讀 1,675評論 4 9
  • 六年春秋已離我而去,我最不能忘懷的則是這校園四季的味道。 ...
    茶新語閱讀 267評論 2 1