1. 程式人生 > >理解DOM事件流

理解DOM事件流


DOM事件標準定義了兩種事件流,這兩種事件流有著顯著的不同並且可能對你的應用有著相當大的影響。這兩種事件流分別是捕獲和冒泡。和許多Web技術一樣,在它們成為標準之前,Netscape和微軟各自不同地實現了它們。Netscape選擇實現了捕獲事件流,微軟則實現了冒泡事件流。幸運的是,W3C決定組合使用這兩種方法,並且大多數新瀏覽器都遵循這兩種事件流方式。

預設情況下,事件使用冒泡事件流,不使用捕獲事件流。然而,在Firefox和Safari裡,你可以顯式的指定使用捕獲事件流,方法是在註冊事件時傳入useCapture引數,將這個引數設為true。下面用個例子分別來測試這兩種事件流。

1、冒泡事件流
當事件在某一DOM元素被觸發時,例如使用者在客戶名位元組點上點選滑鼠,事件將跟隨著該節點繼承自的各個父節點冒泡穿過整個的DOM節點層次,直到它遇到依附有該事件型別處理器的節點,此時,該事件是onclick事件。在冒泡過程中的任何時候都可以終止事件的冒泡,在遵從W3C標準的瀏覽器裡可以通過呼叫事件物件上的stopPropagation()方法,在Internet Explorer裡可以通過設定事件物件的cancelBubble屬性為true。如果不停止事件的傳播,事件將一直通過DOM冒泡直至到達文件根。

測試的HTML檔案,其中用到了mootools-release-1.11.js,對mootools的程式碼進行了改動:
addListener: function(type, fn,setCapture){
            
if (this.addEventListener) this.addEventListener(type, fn, setCapture);
            
else{
                
this.attachEvent('on'+ type, fn);
                
if (setCapture) this.setCapture(true);
            }

            
returnthis;
        }
 
給addListener方法裡增加了setCapture引數,用於測試捕獲事件流。
<body>
<div  id="dd1-ct" style="width:400px;height:400px;border:1px solid #999;padding:2px">Container
    
<div id="dd1-item1" style="width:200px;height:200px;border:1px solid #999;padding:2px">Item1
        
<div  id="dd1-item2" style="width:100px;height:100px;border:1px solid #999;padding:2px"
>Item2</div>
    
</div>  
</div>
<div id='rh'></div>
</body>
效果:

js:
fn1=function(e){
//    e.stopPropagation();
    $('rh').innerHTML+='Item1 clicked!******';
}
;

fn2
=function(e){
//    e.stopPropagation();
    $('rh').innerHTML+='Item2 clicked!-------';
}
;

fn
=function(e){
//    e.stopPropagation();
    $('rh').innerHTML+='Container clicked!&&&&&&&&';
}
;
   
$(
'dd1-item2').addListener('click', fn2.bindWithEvent(),false);       
$(
'dd1-item1').addListener('click', fn1.bindWithEvent(),false);
$(
'dd1-ct').addListener('click', fn.bindWithEvent(),false);

測試結果ie和ff下效果一致:單擊item2,會依次觸發fn2、fn1、fn;單擊item1,會依次觸發fn1、fn;單擊Container,只會觸發fn;當在任何一個事件處理器裡呼叫e.stopPropagation();都會阻止事件的冒泡。

2、捕獲事件流
事件的處理將從DOM層次的根開始,而不是從觸發事件的目標元素開始,事件被從目標元素的所有祖先元素依次往下傳遞。在這個過程中,事件會被從文件根到事件目標元素之間各個繼承派生的元素所捕獲,如果事件監聽器在被註冊時設定了useCapture屬性為true,那麼它們可以被分派給這期間的任何元素以對事件做出處理;否則,事件會被接著傳遞給派生元素路徑上的下一元素,直至目標元素。事件到達目標元素後,它會接著通過DOM節點再進行冒泡。

這裡ie與ff存在著很大的差異,甚至ie6與ie7的表現也各不相同,所以分開測試。

a、ff
  事件從從DOM層次的根開始往下傳遞時,會被useCapture屬性為true的事件監聽器所捕獲,而到達目標元素再從目標元素冒泡時,則會被useCapture屬性為false的事件監聽器所捕獲。當在任何一個事件處理器裡呼叫e.stopPropagation();都會阻止事件的傳播。

b、ie6
用事實說話:

第一種情況:
$('dd1-item2').addListener('click', fn2.bindWithEvent(),true);       
$(
'dd1-item1').addListener('click', fn1.bindWithEvent(),true);
$(
'dd1-ct').addListener('click', fn.bindWithEvent(),true);
單擊瀏覽器的任何位置,都只是觸發fn;

第二種情況:
$('dd1-item2').addListener('click', fn2.bindWithEvent(),true);       
$(
'dd1-item1').addListener('click', fn1.bindWithEvent(),true);
$(
'dd1-ct').addListener('click', fn.bindWithEvent(),false);
單擊瀏覽器的任何位置,會依次觸發fn1、fn;

第三種情況:
$('dd1-item2').addListener('click', fn2.bindWithEvent(),true);       
$(
'dd1-item1').addListener('click', fn1.bindWithEvent(),false);
$(
'dd1-ct').addListener('click', fn.bindWithEvent(),false);
單擊瀏覽器的任何位置,會依次觸發fn2、fn1、fn;

結論:如果HTML元素捕獲了通過該元素的setCapture()方法對這個元素的設定,依附於該元素的處理器將會被事件觸發,即使setCapture()方法被呼叫的這個元素不在目標元素的祖先路徑中。事實上你甚至單擊瀏覽器的非頁面部分都會觸發事件處理器。並且事件一旦被捕獲就不會繼續再往下傳播(即使該元素在目標元素的祖先路徑中),而是立刻冒泡。e.stopPropagation();會阻止事件的冒泡。
 
c、ie7
  測試效果與冒泡事件流一致。將對捕獲事件流的支援幹掉了?
 
結論:正如mootools所做的,避免捕獲事件流。