剛接觸 js 的那個時候,啥也不懂,隻想着如何利用 google、百度到的函數來解決實際的問題,不會想到去一探究竟。
漸漸的,對 js 的語言的不斷深入,有機會去了解一些原理性東西。最近在看 jquery 源碼,感觸很多,總想着用原生的 js
去實作自己的一個 jquery 庫。說實在的,jquery 裡面很多函數和思路,是千百開源工作者長期的貢獻,哪能是短時間就能消化的了。
最近再次碰到 addeventlistener函數(mdn 上關于 addeventlistener 的介紹,很詳細),由于之前并沒有弄懂第三個參數的含義,要麼預設值,要麼手動設定成 false。這次看了不少文章,徹底把事件冒泡和捕獲弄懂。
什麼事件冒泡與捕獲
事件冒泡與捕獲是 dom 中事件傳播的兩種方式,比如說對于注冊了相同僚件的兩個 dom 元素(簡單點就是兩個 div,一裡一外),當點選裡層 div 的時候,這兩個事件誰先執行。
冒泡事件,由裡向外,最裡層的元素先執行,然後冒泡到外層。
捕獲事件,由外向裡,最外層的元素先執行,然後傳遞到内部。
在 ie 9 之前是隻支援事件冒泡,ie 9(包括 ie 9) 之後和目前主流的浏覽器都同時支援兩種事件。
如何設定,隻需修改 addeventlistener的第三個參數,true 為捕獲,false 為冒泡,預設為冒泡。
舉個簡單的例子,
<div>
<span class="out">
<span class="in"></span>
</span>
</div>
<script type="text/javascript">
var dom_out = document.getelementsbyclassname('out')[0];
var dom_in = document.getelementsbyclassname('in')[0];
dom_out.addeventlistener('click',function(){
alert('out');
},false);
dom_in.addeventlistener('click',function(){
alert('in');
</script>
在上面這個例子中,事件是按照冒泡來執行的,點選裡層的 in,會看到先 alert 的順序是先 "in" 後 "out",如果把事件改成捕獲,alert 的順序又不一樣了。
},true);
上面這個例子是捕獲事件的例子,點選 in效果是不是不一樣呢?
之是以會有冒泡和捕獲事件(像 ie 9 之前的浏覽器不支援捕獲事件,還真是反程式員),畢竟在實際中處理事情肯定有個先後順序,要麼由裡向外,要麼由外向裡,兩者都是必須的。
但有時候為了相容 ie 9 以下版本的浏覽器,都會把第三個參數設定成 false 或者預設(預設就是 false)。
進一步了解冒泡和捕獲
現在已經說清楚冒泡和捕獲,那麼如果同時出現冒泡和捕獲會出現什麼結果?
原來浏覽器處理時間分為兩個階段,捕獲階段和冒泡階段,
先執行捕獲階段,如果事件是在捕獲階段執行的(true 情況),則執行;
然後是冒泡階段,如果事件是在冒泡階段執行的(false 情況),則執行;
來看一看例子就知道了:
<span class="s1">s1
<span class="s2">s2
<span class="s3">s3
</span>
</span>
</div>
這次我們設定三個 span,分别是 s1, s2, s3,然後設定 s1,s3 為冒泡執行,s2 為捕獲執行:
var s1 = document.getelementsbyclassname('s1')[0];
var s2 = document.getelementsbyclassname('s2')[0];
var s3 = document.getelementsbyclassname('s3')[0];
s1.addeventlistener('click',function(){
alert('s1');
s2.addeventlistener('click',function(){
alert('s2');
s3.addeventlistener('click',function(){
alert('s3');
從運作的效果來看,點選 s3,依次 alert s2 => s3 => s1,說明:
捕獲事件和冒泡事件同時存在的,而且捕獲事件先執行,冒泡事件後執行;
如果元素存在事件且事件的執行時間與目前邏輯一緻(冒泡或捕獲),則執行。
預設事件取消與停止冒泡
當然,有時候我們隻想執行最内層或最外層的事件,根據内外層關系來把範圍更廣的事件取消掉(對于新手來說,不取消冒泡,很容易中招的出現
bug)。event.stoppropagation()(ie 中window.event.cancelbubble =
true)可以用來取消事件冒泡。
有時候對于浏覽器的預設事件也需要取消,這時候用到的函數則是 event.preventdefault()(ie 中window.event.returnvalue = false)。
那麼預設事件取消和停止冒泡有什麼差別呢?我的了解:浏覽器的預設事件是指浏覽器自己的事件(這不廢話嗎),比如 a 标簽
的點選,表單的送出等,取消掉就不會執行啦;冒泡則取消的是由外向裡(捕獲)、由裡向外(冒泡),stop
之後,就不會繼續周遊了。stackoverflow 上的解答
看下例子,依舊是上面那個例子,不過每個函數都加了 停止冒泡:
s1.addeventlistener('click',function(e){
e.stoppropagation();
alert('s1');
},false);
s2.addeventlistener('click',function(e){
alert('s2');
},true);
s3.addeventlistener('click',function(e){
alert('s3');
},false);
點選的結果是:當點選 s2 或 s3 的時候,都會 alert s2,點選 s1,彈出 s1。因為事件被取消的緣故,點選 s3,執行 s2後就不會在向下執行了。
在看一個 preventdefault 的例子。
<a href="/">點我回首頁</a>
<a href="/" class="back">點我不回首頁</a>
var back = document.getelementsbyclassname('back')[0];
back.addeventlistener('click', function(e){
e.preventdefault();
});
第二個連結是不是回不了首頁,因為浏覽器的預設事件被取消了。
以上所有例子請在非低版本 ie 浏覽器的環境下浏覽 o_o
總結
總結就補充兩個相容 ie 的函數吧:
function stopbubble(e) {
//如果提供了事件對象,則這是一個非ie浏覽器
if ( e && e.stoppropagation )
//是以它支援w3c的stoppropagation()方法
e.stoppropagation();
else
//否則,我們需要使用ie的方式來取消事件冒泡
window.event.cancelbubble = true;
}
//阻止浏覽器的預設行為
function stopdefault( e ) {
//阻止預設浏覽器動作(w3c)
if ( e && e.preventdefault )
e.preventdefault();
//ie中阻止函數器預設動作的方式
window.event.returnvalue = false;
return false;
}
共勉!
作者:songjz
來源:51cto