冒泡,捕獲和事件委托

0.起因

前幾天寫業(yè)務(wù)的時候,碰到了這樣的需求:

bug

checkbox在父DIV的里面,但是checkbox綁定了v-model,要求點擊圖片的時候讓checkbox狀態(tài)改變.

這個需求說起來挺簡單的,綁定之后寫一個方法,讓checkbox的值跟著走就行了.但是我碰到了幾個小問題.解決不難,但是我希望能夠進行輸出并追根問底,形成自己的知識體系.

那么,開始吧.

1.修改狀態(tài)帶來的問題.

input中checkbox的代碼簡化如下:

<input type="checkbox"  v-model="pro.IsSelect" @click.stop="test(pro)">

已知這個checkbox在一個大的div內(nèi),但是現(xiàn)在的問題是,大的DIV上綁定了一個這樣的事件:

prochange:function(data){
      data.IsSelect=!data.IsSelect;
    },

可以看到,我使用這一個事件進行checkbox狀態(tài)的修改
這樣會產(chǎn)生一個問題,那就是我在點擊大DIV的時候會正常觸發(fā)事件,但是點擊小的checkbox則不會.

解決方法很簡單,在小事件上加上阻止事件冒泡即可.

.....@click.stop="test(pro)">

這樣就解決了.但是我心中的疑惑還是沒有消除.冒泡究竟是怎么樣的?我粗淺的知識應(yīng)付工作是足夠了,但是我真的了解冒泡嗎?

2.冒泡和捕獲,關(guān)于事件監(jiān)聽

解決問題的第一個方法當然是找資料.我之前進行過捕獲冒泡相關(guān)知識的學(xué)習(xí),但是印象并不是很深刻,我覺得我有必要重新總結(jié)一下.

2.1鼠標點擊事件的區(qū)別

在我鼠標左鍵點擊網(wǎng)頁的這一刻,究竟發(fā)生了什么?我想大概是:

  1. 系統(tǒng)級API捕捉到了我的鼠標的動作
  2. 系統(tǒng)將這個事件傳遞給瀏覽器.
  3. 瀏覽器通過DOMAPI通知網(wǎng)頁.
  4. 網(wǎng)頁進行事件對應(yīng)的操作.

2.2原始的方法-事件綁定

DOM Level2(鏈接至我認為將DOM level說的比較清楚的文章)之前,其實能夠使用的方法只有事件綁定.

沒錯,就是古老的xxx.onclck方法.

而DOMlevel2的介紹是這樣的:

DOM2級引入了下列新模塊樊诺,也給出了眾多新類型和新接口的定義化焕。

DOM視圖(DOM Views):定義了跟蹤不同文檔(例如,應(yīng)用CSS之前和之后的文檔)視圖的接口愧沟;

DOM事件(DOM Events):定義了事件和事件處理的接口;

DOM樣式(DOM Style):定義了基于CSS為元素應(yīng)用樣式的接口芬为;

DOM遍歷和范圍(DOM Traversal and Range):定義了遍歷和操作文檔樹的接口模闲。

在DOM2添加事件監(jiān)聽之后,我們開始面對著一個新的問題.

2.3child元素和parent元素的通知問題:冒泡和捕獲

要說捕獲和冒泡,得先介紹事件監(jiān)聽忘嫉。

事件監(jiān)聽和綁定其實差不了太多荤牍,但是新增了一個特點:就是無論監(jiān)聽多少次,都不會覆蓋掉前面的事件庆冕。

捕獲和冒泡其實本質(zhì)上只是Child和Parent通知的兩種順序康吵。

捕獲指的是parent先通知,child后通知访递,而冒泡則相反.

比如,就拿業(yè)務(wù)上碰到的這個小問題作為例子.已知Chrome使用的是事件冒泡,那么通知順序應(yīng)該是:

點擊checkbox-通知child(checkbox)值改變-通知parent-觸發(fā)parent綁定的事件-parent上綁定的!IsSelect觸發(fā)-checkbox不變

因為我們在parent上綁定了事件,所以現(xiàn)象是這樣的:

可以看到這個bug的復(fù)現(xiàn).

那么我們說過,要解決這個問題,需要阻止它的事件冒泡.也就是,點擊checkbox不觸發(fā)綁定在parent上的事件,在Vue中的語法糖是@click.stop.

而這個stop實際上便是源生中的e.stopPropagation().

那么捕獲呢?

捕獲的默認效果其實在早期的瀏覽器中比較常見,但是現(xiàn)在依然可以實現(xiàn)用捕獲來代替冒泡.在源生方法監(jiān)聽事件的addEventListener中,我們可以看到API的三個參數(shù)分別是:

target.addEventListener(type, listener[, options]);

我們可以使用第三個參數(shù)來控制冒泡或捕獲.

false:以冒泡方式執(zhí)行事件監(jiān)聽.true:以捕獲方式進行事件監(jiān)聽.

3.事件委托(事件代理)

介紹完上面的晦嵌,事件委托是時候登場了。事件委托簡單說起來就是利用事件冒泡,只指定一個事件處理程序惭载,就可以管理某一類型的所有事件旱函。

網(wǎng)上我找了很多篇博客,基本都是用這個例子描滔,我就直接抄過來了:

有三個同事預(yù)計會在周一收到快遞棒妨。為簽收快遞,有兩種辦法:一是三個人在公司門口等快遞含长;二是委托給前臺MM代為簽收∪唬現(xiàn)實當中,我們大都采用委托的方案拘泞,前臺MM收到快遞后纷纫,她會判斷收件人是誰,然后按照收件人的要求簽收陪腌,甚至代為付款涛酗。這種方案還有一個優(yōu)勢,那就是即使公司里來了新員工(不管多少)偷厦,前臺MM也會在收到寄給新員工的快遞后核實并代為簽收商叹。

看著是不是有點...眼熟?這概念是不是很像工廠模式?

用一個實例來進行說明吧.

<body>
    <ul id="ul1">
        <li>A quick brown</li>
        <li>fox jump over</li>
        <li>the</li>
        <li>lazy dog</li>
    </ul>
</body>
<script>
    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);
            }
        }
    } 
</script>

3-1改進導(dǎo)致的新bug

這是一段標準的源生JS遍歷li,效果是每次點擊li則alert出一個123.但是這么寫明顯很蠢,因為我們監(jiān)聽ul就可以達到同樣的效果.

window.onload = function(){
var oUl = document.getElementById("ul1");
oUl.onclick = function(){
alert(123);
}
嗯,這樣就實現(xiàn)了功能.但是此時出現(xiàn)了一個bug.
當我們點擊的區(qū)域/元素在每個li之外,但是在ul之內(nèi)時,一樣會觸發(fā)事件.
嗯,這樣很討厭.我們想要的是,點擊li才會觸發(fā).
這里有一種Hack方法可以解決,那就是改變ul的樣式,使得ul不會被點擊到,這里就不細說了,說正道.

3-2簡單的處理辦法

正道走下去,會發(fā)現(xiàn)我們需要加一個判斷.寫出來大概是這樣的:

ul.addEventListener('click', function(e) {
                    // 檢查事件源e.targe是否為Li
                    if (e.target && e.target.nodeName.toUpperCase == "LI") {
                        // 真正的處理過程在這里
                        console.log("123");
                    }
                }

加一個簡單的判斷就可以達到我們的目標效果.我能在網(wǎng)上搜索到的博客也基本都是這么做的.
但是這么做真的沒有問題嗎?我很好奇.

如果,點擊到的不是li呢?

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

將HTML改成這樣之后,我們發(fā)現(xiàn),監(jiān)聽失效了,點擊li不會觸發(fā)alert.實際原因是,點擊到的根本不是li,而是span.也就是說,上一段代碼并不優(yōu)雅,它忽略了HTML可能修改的可能性.略微思索之后,我們把JS改成了這樣的:

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

這樣的代碼,才能保證基本的健壯性.
寫到這里,我已經(jīng)沒有任何疑問了.

4.極端情況的探尋

剛準備寫完收工,突然想到了一個讓我有點糾結(jié)的問題,決定親手試一試.

如果同時綁定了捕獲和冒泡,那么執(zhí)行順序?qū)鞘裁礃拥?

<body>
<div id='one'>
    <div id='two'>
        <div id='three'>
            <div id='four'>
            </div>
        </div>
    </div>
</div>

<script type='text/javascript'>
    var one=document.getElementById('one');
    var two=document.getElementById('two');
    var three=document.getElementById('three');
    var four=document.getElementById('four');
    one.addEventListener('click',function(){
        alert('one');
    },false);
    two.addEventListener('click',function(){
        alert('two');
    },false);
    three.addEventListener('click',function(){
        alert('three');
    },false);
    four.addEventListener('click',function(){
        alert('four');
    },false);
</script>

搜了一下,發(fā)現(xiàn)很多人和我有相同的疑慮,找了他們的代碼來手動測試一下.首先是全冒泡的結(jié)果:



可以看到,結(jié)果是one-two/one-three/two/one-four-three-two-one.可以看到,瀏覽器的表現(xiàn)與我們的知識沒有出入.

看到這里,我們可以略過全是捕獲的測試了,能夠預(yù)想到結(jié)果是啥了.

但是,如果冒泡和捕獲同時存在呢?

我們把測試函數(shù)改成這樣:

<script type='text/javascript'>
    var one=document.getElementById('one');
    var two=document.getElementById('two');
    var three=document.getElementById('three');
    var four=document.getElementById('four');
    one.addEventListener('click',function(){
        console.log('one');
    },false);
    two.addEventListener('click',function(){
        console.log('two');
    },true);
    three.addEventListener('click',function(){
        console.log('three');
    },false);
    four.addEventListener('click',function(){
        console.log('four');
    },true);

這樣會輸出怎樣的結(jié)果呢?說實話,我并不知道.所以我決定試試.


這個結(jié)果就有點意思了.點擊two的時候,因為two是捕獲,那么應(yīng)該顯示one-two,但是實際上顯示的是two-one.點擊three的時候,顯示的是two-three-one,點擊four顯示的是two-four-three-one那么這種混雜的情況是怎么運行的呢?

在進行了幾次實驗后,我得出了這個結(jié)論.
在冒泡與捕獲同時存在時,會先執(zhí)行從外到內(nèi)的捕獲,然后再執(zhí)行從內(nèi)到外的冒泡

所以,two-four-three-one應(yīng)該這么解釋:

當點擊four時,優(yōu)先執(zhí)行父級元素捕獲-執(zhí)行two的捕獲-執(zhí)行four的捕獲-執(zhí)行three的冒泡-執(zhí)行one的冒泡.

今天花了很多時間總結(jié)了這個知識,但是并不覺得虧.以后再碰到這種坑,應(yīng)該是再也不會掉進去了.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市只泼,隨后出現(xiàn)的幾起案子剖笙,更是在濱河造成了極大的恐慌,老刑警劉巖请唱,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弥咪,死亡現(xiàn)場離奇詭異,居然都是意外死亡十绑,警方通過查閱死者的電腦和手機聚至,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來本橙,“玉大人扳躬,你說我怎么就攤上這事∩跬ぃ” “怎么了贷币?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長亏狰。 經(jīng)常有香客問我役纹,道長,這世上最難降的妖魔是什么暇唾? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任促脉,我火速辦了婚禮辰斋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瘸味。我一直安慰自己宫仗,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布硫戈。 她就那樣靜靜地躺著锰什,像睡著了一般下硕。 火紅的嫁衣襯著肌膚如雪丁逝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天梭姓,我揣著相機與錄音霜幼,去河邊找鬼。 笑死誉尖,一個胖子當著我的面吹牛罪既,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铡恕,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼琢感,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了探熔?” 一聲冷哼從身側(cè)響起驹针,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诀艰,沒想到半個月后柬甥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡其垄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年苛蒲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绿满。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡臂外,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喇颁,到底是詐尸還是另有隱情寄月,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布无牵,位于F島的核電站漾肮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏茎毁。R本人自食惡果不足惜克懊,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一忱辅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谭溉,春花似錦墙懂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至柜与,卻和暖如春巧勤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弄匕。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工颅悉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人迁匠。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓剩瓶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親城丧。 傳聞我的和親對象是個殘疾皇子延曙,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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