阻止冒泡

在網頁開發的過程當中常常遇到的一個需求就是點擊一div內部作某些操做,而點擊頁面其它地方隱藏該div。好比不少導航菜單,當菜單展開的時候,就會要求點擊頁面其它非菜單地方,隱藏該菜單。javascript

先從最簡單的開始,假如頁面有一個id爲test的div,咱們要實現點擊頁面其它地方隱藏該div:css

<div id="test" style="margin:100px;width:100px;height:100px;">
                        
        </div>

對於這個問題通常有兩種思路,這兩種思路都會利用事件冒泡這一原理,想要詳細瞭解Javascript事件機制能夠看看JavaScript與HTML交互——事件,這不是本文重點,因此這裏只是簡單介紹一下事件冒泡,html

事件冒泡

IE的事件冒泡:事件開始時由最具體的元素接收,而後逐級向上傳播到較爲不具體的元素java

Netscape的事件捕獲:不太具體的節點更早接收事件,而最具體的元素最後接收事件,和事件冒泡相反ajax

DOM事件流:DOM2級事件規定事件流包括三個階段,事件捕獲階段,處於目標階段,事件冒泡階段,首先發生的是事件捕獲,爲截取事件提供機會,而後是實際目標接收事件,最後是冒泡句階段。瀏覽器

Opera、Firefox、Chrome、Safari都支持DOM事件流,IE不支持事件流,只支持事件冒泡函數

若有如下html,點擊div區域,按照不一樣的模型事件元素的click事件觸發順序以下所示:性能

複製代碼
<!DOCTYPE html >
<html>
<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <title>Test Page</title>
</head>
<body>
    <div>
        Click Here</div>
</body>
</html>
複製代碼

image

在觸發DOM上的某個事件的時候會產生一個事件對象event,這個對象包含着全部與事件有關的信息,包括產生事件的元素、事件類型等相關信息。全部瀏覽都支持event對象,但支持方式不一樣。事件對象有一個方法(W3C:stopPropagation)/屬性(IE:cancelBulle=true)能夠阻止事件繼續冒泡或捕獲。咱們若是想在事件冒泡到某元素時阻止冒泡能夠寫一個這樣的兼容瀏覽器方法:優化

複製代碼
function stopPropagation(e) {//把事件對象傳入
            if (e.stopPropagation) //支持W3C標準
                e.stopPropagation();
            else //IE8及如下瀏覽器
                e.cancelBubble = true;
        }
複製代碼

由於全部的瀏覽器都支持事件冒泡,瀏覽器兼容性考慮,咱們通常綁定事件的的時候都會利用事件冒泡而不是事件捕獲。瞭解了這個以後咱們能夠看看下面兩種思路了。ui

思路一

第一種思路分兩步

第一步:對document的click事件綁定事件處理程序,使其隱藏該div

第二步:對div的click事件綁定事件處理程序,阻止事件冒泡,防止其冒泡到document,而調用document的onclick方法隱藏了該div。

複製代碼
<script type="text/javascript">
            function stopPropagation(e) {
                if (e.stopPropagation) 
                    e.stopPropagation();
                else 
                    e.cancelBubble = true;
            }

            $(document).bind('click',function(){
                $('#test').css('display','none');
            });

            $('#test').bind('click',function(e){
                stopPropagation(e);
            });
        </script>
複製代碼

這樣當點擊頁面非div區域的時候,直接或層層冒泡會調用document的onclick方法,隱藏該div,而點擊div或其子元素的時候,事件總會冒泡的div自己,這時候會阻止事件繼續冒泡,不會調用doument的onclick方法導致div被隱藏,從而完成了咱們的需求。

思路二

咱們以前提到,在觸發DOM上的某個事件的時候會產生一個事件對象event,這個對象包含着全部與事件有關的信息,包括產生事件的元素、事件類型等相關信息,思路一中div的click事件處理程序傳入的參數就是這個event對象。訪問IE中的event對象有幾種不一樣的方式,取決於指定事件處理程序的方法。直接爲DOM元素添加事件處理程序時,event對象做爲window對象的一個屬性存在。

event對象包含了一個重要屬性:target(W3C)/srcElement(IE),這個屬性標識了觸發事件的原始元素,思路二就是要利用這個屬性。咱們能夠直接對document的click事件綁定事件處理程序,在事件處理程序中判讀事件源是否爲id==test的div元素或其子元素,若是是則方法return不作操做,若是不是則隱藏該div。

複製代碼
<script type="text/javascript">
            $(document).bind('click',function(e){
                var e = e || window.event; //瀏覽器兼容性
                var elem = e.target || e.srcElement;
                while (elem) { //循環判斷至跟節點,防止點擊的是div子元素
                    if (elem.id && elem.id=='test') {
                        return;
                    }
                    elem = elem.parentNode;
                }

                $('#test').css('display','none'); //點擊的不是div或其子元素
            });
        </script>
複製代碼

這樣當點擊頁面任何地方的時候都會層層冒泡至document的click事件,事件處理程序會判斷事件源是否爲id==test的div或其子元素,若是是方法return,不然隱藏該div,也可以實現咱們的需求。

注意點及優劣

這兩種思路都依賴於事件冒泡,因此咱們在處理其它相關元素的click事件的時候必定要注意這點,避免其餘相關元素的click事件處理程序中包含阻止事件冒泡代碼而影響了該功能。

這兩種方式都很容易理解,貌似思路一更優秀一些,看起來它的處理更簡單一些,不用去層層判斷事件源,直接把click事件綁定在該div上。在這個例子中確實如此,可是有些複雜的頁面就不盡然了,假如咱們有一個頁面,上面有數十個div都須要點擊頁面其它地方隱藏這類問題

複製代碼
<div class="dialogs">
        <div class="dialog">
            <div id="1">1</div>
            <div id="2">2</div>
        </div>
        <div class="dialog">
            <div id="1">1</div>
            <div id="2">2</div>
        </div>
        <div class="dialog">
            <div id="1">1</div>
            <div id="2">2</div>
        </div>
        ...
    </div>
複製代碼

咱們用思路一寫出的代碼多是這樣:

複製代碼
<script type="text/javascript">
            function stopPropagation(e) {
                if (e.stopPropagation) 
                    e.stopPropagation();
                else 
                    e.cancelBubble = true;
            }

            $(document).bind('click',function(){
                $('.dialog').css('display','none');
            });

            $('.dialog').bind('click',function(e){
                stopPropagation(e);
            });
        
        </script>
複製代碼

看起來簡單依舊的樣子,可是咱們仔細想一想就會發現問題,咱們在每一個dialog上都綁定了相似的方法,維護如此多的click事件處理程序對內存來講絕對是可開銷,致使咱們頁面運行緩慢。並且若是咱們能夠動態使用ajax建立新dialog問題又來了,新建立的dialog不能實現隱藏功能!由於綁定函數已經執行完了,不會再爲新的dialog綁定click事件處理程序,咱們只能本身來作此事。也就是說思路一沒法把處理程序附加到可能還未存在於DOM中的DOM元素之上。由於它是直接把處理程序綁定到各個元素上,它不能把處理程序綁定到還未存在於頁面中的元素之上。

這時候就是思路二展現身手的時候了,咱們看看思路二在這種時候代碼的書寫

複製代碼
<script type="text/javascript">
            $(document).bind('click',function(e){
                var e = e || window.event;
                var elem = e.target || e.srcElement;
                while (elem) {
                    if (elem.className && elem.className.indexOf('dialog')>-1) {
                        return;
                    }
                    elem = elem.parentNode;
                }

                $('#test').css('display','none'); 
            });
        </script>
複製代碼

改動也至關的小,咱們來看看是否是能解決上邊的兩個問題了,首先不管多少個dialog咱們只是綁定了一個click事件處理程序,對性能影響不大,添加一個新的dialog思路二的代碼還好很差使呢,依舊好使,這樣咱們就能發如今複雜頁面的狀況下實際上思路二是一種更優秀的解決方案。

這些都明白了,咱們就能說說本文的第二個主角jQuery的delegate方法了。

delegate

首先看看jQuery官方對delegate的語法及描述

.delegate( selector, eventType, handler(eventObject) )

Description: Attach a handler to one or more events for all elements that match the selector, now or in the future, based on a specific set of root elements.

delegate() 方法爲指定的元素(屬於被選元素的子元素)添加一個或多個事件處理程序,並規定當這些事件發生時運行的函數。

使用 delegate() 方法的事件處理程序適用於當前或將來的元素(好比由腳本建立的新元素)。

$( "table" ).delegate( "td", "click", function() {
      $( this ).toggleClass( "chosen" );
    });

經過上面語句咱們就能夠爲全部table的td綁定click事件處理程序。

delegate方法設計意圖在於把處理程序附加到單個元素上或是一小組元素之上,監聽後代元素上的事件而不是循環遍歷並把同一個函數逐個附加到DOM中的多個個元素上。把處理程序附加到一個(或是一小組)祖先元素上而不是直接把處理程序附加到頁面中的全部元素上,從而帶來性能上的優化。

jQuery版隱藏dialog

經過上面知識咱們能夠發現jQuery的delegate方法能夠方便實現咱們隱藏div的需求

<script type="text/javascript">
            $('.dialogs').delegate('.dialog','click',function(){
                $(this).css('display','none');
            });
        </script>

使用jQuery咱們發現比咱們思路二在性能上又有了小幅提高,由於咱們不須要冒泡至document處理了,只須要在dialog的父元素就能夠處理完成了,能夠不至於把不少相似功能都綁定到document上,須要注意的一點就是jQuery已經貼心的幫咱們把this處理爲事件源,處理起來更是如魚得水了。

delegate與bind

經過上面咱們說一堆咱們能夠在權衡使用bind仍是delegate上有必定依據了,若是就單獨綁定一個元素的事件處理程序,用bind仍是很合適的,可是若是處理不少相似元素的事件處理程序的時候不妨考慮一下delegate,看看是否對提升性能有所幫助。