深入研究事件冒泡、事件捕獲和事件委托

以前聽老師講解事件冒泡和事件捕獲機(jī)制的時(shí)候跟聽天書一樣,只依稀記得IE使用的是事件冒泡,其他瀏覽器則是事件捕獲。當(dāng)時(shí)的我,把它當(dāng)成IE瀏覽器兼容問題,所以沒有深究(IE8以下版本的瀏覽器已基本退出市場(chǎng))。工作至今,雖然多次遇到該類問題,但均未深究,始終一知半解,遇到了全靠習(xí)慣性本能地阻止事件冒泡,但是其中核心原理一直沒有去耐心探求。今天閑來無事自己做了個(gè)demo,算是把這個(gè)問題徹底搞明白了。

先上結(jié)論:他們是描述事件觸發(fā)時(shí)序問題的術(shù)語。事件捕獲指的是從總的父級(jí)event.currentTarget到觸發(fā)事件的那個(gè)節(jié)點(diǎn)(event.target),即自上而下的去觸發(fā)事件。相反的,事件冒泡是自下而上的去觸發(fā)事件。綁定事件方法的第三個(gè)參數(shù),就是控制事件觸發(fā)順序是否為事件捕獲。true,事件捕獲;false,事件冒泡。默認(rèn)false,即事件冒泡。

一、冒泡機(jī)制
什么是冒泡呢?
氣泡從水底開始往上升,由深到淺,升到最上面。在上升的過程中,氣泡會(huì)經(jīng)過不同深度層次的水。相對(duì)應(yīng)地:這個(gè)氣泡就相當(dāng)于我們這里的事件,而水則相當(dāng)于我們的整個(gè)dom樹;事件從dom 樹的底層 層層往上傳遞,直至傳遞到dom的根節(jié)點(diǎn)。
Demo1:
定義一個(gè)html, 里面有三個(gè)簡(jiǎn)單的dom 元素:div1,div2, span,div2包含span,div1 包含div2;而它們都在body 下:

<span style="font-family:Microsoft YaHei;font-size:10px;"><body id="body">  
   <div id="box1" class="box1">  
       <div id="box2" class="box2">  
           <span id="span">This is a span.</span>  
       </div>  
   </div>  
</body></span>  

在這個(gè)基礎(chǔ)上,我們實(shí)現(xiàn)下面的功能:
a.body添加 click 事件監(jiān)聽,當(dāng)body捕獲到event事件時(shí),打印出事件發(fā)生的時(shí)間和 觸發(fā)事件的節(jié)點(diǎn)信息:

<script type="text/javascript">  
   window.onload = function() {  
       document.getElementById("body").addEventListener("click",eventHandler);  
   }  
   function eventHandler(event) {  
       console.log("時(shí)間:"+new Date(event.timeStamp)+" 產(chǎn)生事件的節(jié)點(diǎn):" + event.target.id +"  當(dāng)前節(jié)點(diǎn):"+event.currentTarget.id);  
   }  
</script>

當(dāng)我們依次點(diǎn)擊"This is span",div2,div1,body后,輸出以下信息:

分析以上的結(jié)果:
** ** 無論是body,body 的子元素div1,還是 div的子元素div2,還有 span, 當(dāng)這些元素被點(diǎn)擊click時(shí),都會(huì)產(chǎn)生click事件,并且body都會(huì)捕獲到,然后調(diào)用相應(yīng)的事件處理函數(shù)。就像水中的氣泡從底往上冒一樣,事件也會(huì)往上傳遞。
事件傳遞的示意圖如下所示:

demo1.jpg

一般地,事件在傳遞過程中會(huì)有一些信息,這些是事件的組成部分:事件發(fā)生的時(shí)間+事件發(fā)生的地點(diǎn)+ 事件的類型+事件的當(dāng)前處理者+其他信息,

事件冒泡傳遞方向.jpg

Demo2:

<html>  
<head>  
<meta charset="UTF-8">  
<script type="text/javascript" src="js/jquery-1.11.0.js"></script>  
<title>Insert title here</title>  
<style type="text/css">  
.box1 {  
    border: green 40px solid;  
    width: 300px;  
    height: 300px;  
    margin: auto;  
}  
  
.box2 {  
    border: yellow 40px solid;  
    width: 220px;  
    height: 220px;  
    margin: auto;  
}  
  
span {  
    position: relative;  
    left: 50px;  
    top: 50px;  
    background-color: rgba(128, 128, 128, 0.22);  
}  
</style>  
  
<script type="text/javascript">  
    window.onload = function() {  
        document.getElementById("body").addEventListener("click",eventHandler);  
    }  
    function eventHandler(event) {  
        console.log("時(shí)間:"+new Date(event.timeStamp)+" 產(chǎn)生事件的節(jié)點(diǎn):" + event.target.id +"  當(dāng)前節(jié)點(diǎn):"+event.currentTarget.id);  
    }  
</script>  
  
</head>  
<body id="body">  
    <div id="box1" class="box1">  
        <div id="box2" class="box2">  
            <span id="span">This is a span.</span>  
        </div>  
    </div>  
</body>  
</html>

利用事件冒泡實(shí)現(xiàn):

$("ul").on("mouseover",function(e){
       $(e.target).css("background- color","#ddd").siblings().css("backgroundcolor","white");
})

也許有人會(huì)說,我們直接給所有l(wèi)i都綁上事件也可以啊,一點(diǎn)也不麻煩,只要……

$("li").on("mouseover",function(){
                $(this).css("background-color","#ddd").siblings().css("background-color","white");
})

是,這樣也行。而且從代碼簡(jiǎn)潔程度上,兩者是相若仿佛的。但是,前者少了一個(gè)遍歷所有l(wèi)i節(jié)點(diǎn)的操作,所以在性能上肯定是更優(yōu)的。
還有就是,如果我們?cè)诮壎ㄊ录瓿珊螅撁嬗謩?dòng)態(tài)的加載了一些元素的話就有些復(fù)雜或者有可能忘記。

二、阻止事件冒泡
我們現(xiàn)在想實(shí)現(xiàn)這樣的功能,在div1 點(diǎn)擊的時(shí)候,彈出 "你好,我是最外層div。",點(diǎn)擊div2 的時(shí)候,彈出 "你好,我是第二層div";點(diǎn)擊span 的時(shí)候,彈出"您好,我是span。"。
由此我們會(huì)有下面的JavaScript片段:

<script type="text/javascript">  
    window.onload = function() {  
        document.getElementById("box1").addEventListener("click",function(event){  
            alert("您好,我是最外層div。");  
        });  
        document.getElementById("box2").addEventListener("click",function(event){  
            alert("您好,我是第二層div。");  
        });  
        document.getElementById("span").addEventListener("click",function(event){  
            alert("您好,我是span。");  
        });  
    }  
</script>

上述代碼會(huì)單擊span 的時(shí)候,會(huì)出來一個(gè)彈出框 "您好,我是span。","您好,我是第二層div。","您好,我是span。"。這顯然不是我們想要的! 我們希望的是點(diǎn)誰顯示誰的信息而已。為什么會(huì)出現(xiàn)上述的情況呢? 原因就在于事件的冒泡,點(diǎn)擊span的時(shí)候,span 會(huì)把產(chǎn)生的事件往上冒泡,作為父節(jié)點(diǎn)的div2 和 祖父節(jié)點(diǎn)的div1也會(huì)收到此事件,于是會(huì)做出事件響應(yīng),執(zhí)行響應(yīng)函數(shù)。現(xiàn)在問題是發(fā)現(xiàn)了,但是怎么解決呢?

方法一:我們來考慮一個(gè)形象一點(diǎn)的情況:水中的一個(gè)氣泡正在從底部往上冒,而你現(xiàn)在在水中,不想讓這個(gè)氣泡往上冒,怎么辦呢?——把它扎破!沒了氣泡,自然不會(huì)往上冒了。類似地,對(duì)某一個(gè)節(jié)點(diǎn)而言,如果不想它現(xiàn)在處理的事件繼續(xù)往上冒泡的話,我們可以終止冒泡:
在相應(yīng)的處理函數(shù)內(nèi),加入 event.stopPropagation() ,終止事件的廣播分發(fā),這樣事件停留在本節(jié)點(diǎn),不會(huì)再往外傳播了。修改上述的script片段:

<script type="text/javascript">  
   window.onload = function() {  
       document.getElementById("box1").addEventListener("click",function(event){  
           alert("您好,我是最外層div。");  
           event.stopPropagation();  
       });  
       document.getElementById("box2").addEventListener("click",function(event){  
           alert("您好,我是第二層div。");  
           event.stopPropagation();  
       });  
       document.getElementById("span").addEventListener("click",function(event){  
           alert("您好,我是span。");  
           event.stopPropagation();  
       });  
   }  
</script>

經(jīng)過這樣一段代碼,點(diǎn)擊不同元素會(huì)有不同的提示,不會(huì)出現(xiàn)彈出多個(gè)框的情況了。

       方法二:事件包含最初觸發(fā)事件的節(jié)點(diǎn)引用 和 當(dāng)前處理事件節(jié)點(diǎn)的引用,那如果節(jié)點(diǎn)只處理自己觸發(fā)的事件即可,不是自己產(chǎn)生的事件不處理。event.target 引用了產(chǎn)生此event對(duì)象的dom 節(jié)點(diǎn),而event.currrentTarget 則引用了當(dāng)前處理節(jié)點(diǎn),我們可以通過這 兩個(gè)target 是否相等。
        比如span 點(diǎn)擊事件,產(chǎn)生一個(gè)event 事件對(duì)象,event.target 指向了span元素,span處理此事件時(shí),event.currentTarget 指向的也是span元素,這時(shí)判斷兩者相等,則執(zhí)行相應(yīng)的處理函數(shù)。而事件傳遞給 div2 的時(shí)候,event.currentTarget變成 div2,這時(shí)候判斷二者不相等,即事件不是div2 本身產(chǎn)生的,就不作響應(yīng)處理邏輯。
<script type="text/javascript">  
    window.onload = function() {  
        document.getElementById("box1").addEventListener("click",function(event){  
            if(event.target == event.currentTarget)  
            {  
                alert("您好,我是最外層div。");  
            }  
        });  
        document.getElementById("box2").addEventListener("click",function(event){  
            if(event.target == event.currentTarget)  
            {  
                alert("您好,我是第二層div。");  
            }  
        });  
        document.getElementById("span").addEventListener("click",function(event){  
            if(event.target == event.currentTarget)  
            {  
                alert("您好,我是span。");  
                  
            }  
        });  
    }  
</script>

比較:
從事件傳遞上看:方法一在于取消事件冒泡,即當(dāng)某些節(jié)點(diǎn)取消冒泡后,事件不會(huì)再傳遞;方法二在于不阻止冒泡,過濾需要處理的事件,事件處理后還會(huì)繼續(xù)傳遞;
** 優(yōu)缺點(diǎn):**

方法一缺點(diǎn):為了實(shí)現(xiàn)點(diǎn)擊特定的元素顯示對(duì)應(yīng)的信息,方法一要求每個(gè)元素的子元素也必須終止事件的冒泡傳遞,即跟別的元素功能上強(qiáng)關(guān)聯(lián),這樣的方法會(huì)很脆弱。比如,如果span 元素的處理函數(shù)沒有執(zhí)行冒泡終止,則事件會(huì)傳到div2 上,這樣會(huì)造成div2 的提示信息;

方法二缺點(diǎn):方法二為每一個(gè)元素都增加了事件監(jiān)聽處理函數(shù),事件的處理邏輯都很相似,即都有判斷 if(event.target == event.currentTarget),這樣存在了很大的代碼冗余,現(xiàn)在是三個(gè)元素還好,當(dāng)有10幾個(gè),上百個(gè)又該怎么辦呢?
還有就是為每一個(gè)元素都有處理函數(shù),在一定程度上增加邏輯和代碼的復(fù)雜度。

我們?cè)賮矸治鲆幌路椒ǘ悍椒ǘ脑硎?元素收到事件后,判斷事件是否符合要求,然后做相應(yīng)的處理,然后事件繼續(xù)冒泡往上傳遞;

既然事件是冒泡傳遞的,那可不可以讓某個(gè)父節(jié)點(diǎn)統(tǒng)一處理事件,通過判斷事件的發(fā)生地(即事件產(chǎn)生的節(jié)點(diǎn)),然后做出相應(yīng)的處理呢?答案是可以的,下面通過給body 元素添加事件監(jiān)聽,然后通過判斷event.target 然后對(duì)不同的target產(chǎn)生不同的行為。
** 將方法二的代碼重構(gòu)一下:**

<script type="text/javascript">  
    window.onload = function() {  
        document.getElementById("body").addEventListener("click",eventPerformed);  
    }  
    function eventPerformed(event) {  
        var target = event.target;  
        switch (target.id) {  
        case "span":   
            alert("您好,我是span。");  
            break;  
        case "div1":  
            alert("您好,我是第二層div。");  
            break;  
        case "div2":  
             alert("您好,我是最外層div。");  
            break;  
        }  
    }  
</script>  

結(jié)果會(huì)是點(diǎn)擊不同的元素,只彈出相符合的提示,不會(huì)有多余的提示。

通過以上方式,我們把本來每個(gè)元素都要有的處理函數(shù),都交給了其祖父節(jié)點(diǎn)body 元素來完成了,也就是說,span,div2,div1 將自己的響應(yīng)邏輯委托給body,讓它來完成相應(yīng)邏輯,自己不實(shí)現(xiàn)相應(yīng)邏輯,這個(gè)模式,就是所謂的事件委托

感謝作者亦山,最近自己能夠耐下心來去看一些研究性的帖子了,很有趣,雖然有時(shí)候只是一些很細(xì)微的小問題,但是由于大家平時(shí)的忽略,由于對(duì)這些小的問題不夠深入的理解,有時(shí)候真的會(huì)出人命的啊。
原文地址:http://blog.csdn.net/luanlouis/article/details/23927347

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評(píng)論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,727評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,193評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,411評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,777評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,978評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,216評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,657評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,960評(píng)論 2 373

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

  • 一、什么是事件冒泡 在一個(gè)對(duì)象上觸發(fā)某類事件(比如單擊onclick事件),如果此對(duì)象定義了此事件的處理程序,那么...
    老夫撩發(fā)少年狂閱讀 895評(píng)論 0 1
  • 以下文章為轉(zhuǎn)載,對(duì)理解JavaScript中的事件處理機(jī)制很有幫助,淺顯易懂,特分享于此。 什么是事件? 事件(E...
    jxyjxy閱讀 3,057評(píng)論 1 10
  • jQuery基礎(chǔ) 什么是JQ?一個(gè)優(yōu)秀的JS庫,大型開發(fā)必備JQ的好處?一簡(jiǎn)化JS的復(fù)雜操作二不再需要關(guān)心兼容性三...
    幺七閱讀 952評(píng)論 0 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,782評(píng)論 18 139
  • 忙碌的一天結(jié)束了,感恩時(shí)間!感恩生命!感恩媽媽!感恩生活中的酸甜苦辣澀!感恩生命中出現(xiàn)的每個(gè)人!感恩帶給我成長(zhǎng)的經(jīng)...
    寬兩秒心自在閱讀 184評(píng)論 0 0