有關jQuery 事件模塊結構部分的分析可以參考這篇文章惜纸,作者分析的很不錯,贊一個绝骚。
進題之前耐版,有幾個名詞 EventTarget,EventListener压汪,Event講一下粪牲,文章中會用到
var t = document.getElementsByTagName('div')[0],
fn = function( e ){
console.log( e.type) ;
};
t.addEventListener('click' , fn , false);
解釋上面代碼就是:EventTarget為t的元素div注冊了一個EventType為click的事件監(jiān)聽EventListener fn,事件監(jiān)聽函數fn有一個Event類型的參數e
下面的代碼有寫過沒止剖?
jquery事件綁定相關的核心API腺阳,代碼中蘊含了一種設計邏輯:對于from表單的submit事件,委托給body元素穿香,由fn監(jiān)聽函數去處理亭引,這種設計模式稱為事件代理。
不清楚下面代碼邏輯的皮获,可以參考這里 delegate焙蚓,live,on,三者之間的區(qū)別可以看這篇文章
$("body").delegate( "form", "submit" , fn );
$("body").live( "submit" , fn );
$("body").on( "submit" , "form", fn );
什么是事件代理主届?
舉個例子:假設元素E的上級元素F監(jiān)聽了click事件赵哲,處理函數為fn,元素E并沒有監(jiān)聽click事件君丁,鼠標點擊了元素E,由于事件冒泡原理将宪,fn會因為E的click操作而被執(zhí)行绘闷,這一過程描述的就是F代理了E的click事件
如下圖:
要想這一過程有意義,EventListenerfn需要具備一個能力:在fn中能夠訪問到觸發(fā)事件的元素E较坛,為什么印蔗?因為大多情況下需要改變的是觸發(fā)事件的元素的屬性,而不是事件代理本身丑勤。
要訪問EventTarget E华嘹,可以這樣做:
e = e || window.event ;
target = e.target || e.srcElement
事件代理帶來了什么好處
看下圖
E1 , E2…En是F的子元素,假設子元素觸發(fā)的事件都是相同的法竞,按照逐一綁定事件的做發(fā)耙厚,代碼就是下面的樣子,不夠優(yōu)雅不夠節(jié)約
E1.bind( 'eventType' , eventListioner );
E2.bind( 'eventType' , eventListioner );
......
En.bind( 'eventType' , eventListioner );
有了事件代理,注冊事件的代碼就變成這樣子了
F.delegate( 'eventType' , eventListioner );
function eventListioner ( e ){
if(e.target.tagName == E){
....
}
}
還沒完岔霸,前面的一丟丟都是在假設事件是可以冒泡的前提下薛躬,是否存在不冒泡的事件呢?如果存在呆细,那代理模型應用在DOM事件模型上無疑存在缺陷型宝,不好意思,input元素的focus事件默認是不冒泡的絮爷,為什么呢趴酣?ppk前輩也沒找到根源,但是與對其他元素影響較小不無關系坑夯。其他不冒泡的事件可以參考這里
模擬事件冒泡
既然存在不冒泡的事件岖寞,為了確保瀏覽器之間的兼容,就需要為不冒泡的事件添加冒泡特性渊涝。
冒泡的本質是什么慎璧?一句話描述冒泡的過程就是:事件源(EventTarget)A觸發(fā)一事件E,瀏覽器會檢查EventTarget A是否有注冊對應的事件處理函數跨释,如果有對應的處理函數胸私,則調用,否則鳖谈,事件傳遞到父節(jié)點AF上岁疼,然后重復前一過程直至事件傳遞到Window對象終止。
其本質就是事件在DOM元素上自下而上的傳遞事件,執(zhí)行事件監(jiān)聽的過程,清楚了原理捷绒,模擬事件冒泡的算法也就有了(simulate_bubble_algo):
算法以事件觸發(fā)者為起點瑰排,以window對象為終點,在DOM層級樹上做遍歷暖侨,判斷DOM元素是否有注冊對應EventType的事件監(jiān)聽椭住。
jQuery中讓input元素的focus事件冒泡做法, jQuery 利用了瀏覽器之間對事件支持的不同.
對于IE,jQuery直接使用IE中已經支持冒泡的事件focusin,對于用戶想要注冊綁定的focus事件字逗,直接將其綁定到focusin事件上實現事件冒泡,
對于非IE瀏覽器京郑,jQuery在focus事件捕獲階段為document綁定監(jiān)聽函數,該函數實現simulate_bubble_algo算法葫掉,對DOM樹的遍歷些举,逐一觸發(fā)滿足條件的元素的事件監(jiān)聽函數。
還沒完俭厚,IE<9中form元素的submit事件也是不冒泡的户魏,與focus事件相比,為IE添加冒泡的submit事件似乎更為緊迫挪挤。
為了描述jQuery中的實現方案叼丑,先要解釋一些現象:
- 理解事件的默認行為
HTML中type為submit的兩類元素input和button,與其他類型非submit的元素相比較电禀,他們多的是有一個submit form表單的默認行為
<input id="asubmitInput" type="submit" />
<button id="asubmitButton" type="submit" />
- 理解 event.preventDefault()的作用(IE中對應是event.returnValue), Event對象的該方法/屬性用于阻止瀏覽器執(zhí)行事件的默認行為幢码。
OK,jQuery如何實現submit事件的冒泡呢尖飞?
想象一下button提交表單的過程(submit_event_flow):
鼠標點擊類型為submit的button症副,觸發(fā)click事件
如果button有對應的click事件監(jiān)聽,則執(zhí)行監(jiān)聽函數政基,否則click事件冒泡到上級元素
2 中click事件監(jiān)聽函數執(zhí)行完畢返回贞铣,若返回值為非false,click事件冒泡到上級元素沮明,否則click事件冒泡終止辕坝,表單提交動作不執(zhí)行(默認行為不執(zhí)行)
事件冒泡到上級元素,重復過程 2, 3荐健,直至遍歷到window對象或者中途監(jiān)聽函數返回false中止酱畅。
事件冒泡到頂層元素window,如返回值不為false江场,則執(zhí)行默認行為纺酸,對應為觸發(fā)form表單的submit事件
由于submit事件在IE中不冒泡,所以至此form的submit操作結束址否。
驗證這一過程可以使用下面的腳本
$(function(){
var elem = document.getElementsByTagName("input")[0];
var aform = document.getElementsByTagName("form")[0];
if(elem.addEventListener){
aform.addEventListener("submit" , function(){
console.log("form submit fired");
});
window.addEventListener("click" , function(){
console.log("window click fn fired");
});
document.addEventListener("click" , function(){
console.log("document click fn fired");
});
document.documentElement.addEventListener("click" , function(){
console.log("html click fn fired");
});
document.body.addEventListener("click" , function(){
console.log("body click fn fired");
});
elem.addEventListener("click" , function(e){
console.log("input click fn fired");
});
}
});
<form action='#' >
<input type="submit"/>
</form>
如何修復IE中的這一問題餐蔬,使得IE的submit事件冒泡呢?jQuery的方案如下:
文檔加載時,如果存在form表單的事件代理樊诺,為代理對象delegateObj綁定click仗考,keypress事件監(jiān)聽fn
button觸發(fā)click或者keypress事件后,事件冒泡到delegateObj词爬,觸發(fā)監(jiān)聽函數fn秃嗜,fn中找到事件源對應的form元素,為form元素綁定submit事件監(jiān)聽函數fn_submit顿膨,
click事件冒泡到頂層元素window對象痪寻,執(zhí)行submit類型click事件的默認行為
表單提交事件被觸發(fā),執(zhí)行fn_submit監(jiān)聽函數虽惭,該監(jiān)聽函數修改Event對象屬性,確保事件分發(fā)過程中執(zhí)行一次simulate_bubble_algo算法蛇尚。
事件冒泡的一個意外
因為事件冒泡機制的原因芽唇,mouseover和mouseout事件中對鼠標位置的正確判斷變得復雜,為此W3C標準中新加入了默認不冒泡的鼠標事件mouseenter和mouseleave取劫,但是瀏覽器廠商似乎并不買賬匆笤,遲遲并未實現新的鼠標事件,為此PPK在其Blog上大吐苦水谱邪,希望瀏覽器廠商把這事當回事炮捧,畢竟用戶需要不冒泡的mouseover和mouseout事件。
講了這麼多惦银,描述一下mouseover 和 mouseout實際開發(fā)中遇到的問題咆课。下面文字絕大部分取自PPK的博客該篇文章
假設要監(jiān)聽ev4元素的鼠標的mouseover事件,圖1鼠標從ev3移動到ev4扯俱,圖二鼠標從span移動到ev4,盡管事件最終都是在ev4元素上觸發(fā)书蚪,但知道鼠標從哪里來很有必要,為此W3C定義了一個屬性relatedTarget迅栅,對于mouseover事件殊校,這個屬性記錄鼠標從哪里來,對于mouseout記錄鼠標去了哪里读存,但低版本IE并不支持該屬性为流,好在IE有對應的屬性fromElement/toElement.
看下圖,程序在layer上注冊了mouseout事件让簿,監(jiān)聽鼠標是否離開layer敬察,但有一情況,例如鼠標從Link移動到layer的過程中拜英,由于事件冒泡静汤,也會導致綁定在layer上的事件監(jiān)聽器doSomething被執(zhí)行,初衷是監(jiān)聽鼠標離開layer,但實際上鼠標并未離開layer就執(zhí)行了事件監(jiān)聽虫给。
如何解決這一問題藤抡?PPK給出了一個解決方案,思路就是要根據event的屬性target和relatedTarget綜合判斷
鼠標離開layer抹估,target必須為layer
1成立缠黍,判斷relatedTarget對象和target之間的關系,target不能是relatedTarget的祖先元素
實現的代碼如下:
function doSomething(e) {
if (!e) var e = window.event;
var tg = (window.event) ? e.srcElement : e.target;
if (tg.nodeName != 'DIV') {
return;
}
var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
while (reltg != tg && reltg.nodeName != 'BODY'){
reltg= reltg.parentNode if (reltg== tg)
}
return;
}
世界上總是不缺少有心人药蜻,盡管除了IE外其他瀏覽器廠商并沒有實現mouseenter和mouseleave事件瓷式,但是jQuery卻在其代碼中為我們模擬出了mouseenter和mouseleave事件。