jQuery原始碼分析之jQuery.event.trigger及jQuery.Event十問
問題1:jQuery.event.trigger常見呼叫方式有那些?
例項trigger方法
trigger: function( type, data ) {
return this.each(function() {
jQuery.event.trigger( type, data, this );//呼叫了jQuery.event.trigger方法
});
}
例項triggerHandle方法
ajax方法中的呼叫方式triggerHandler: function( type, data ) { var elem = this[0]; if ( elem ) { return jQuery.event.trigger( type, data, elem, true );//jQuery.event.trigger呼叫 } }
jQuery.event.trigger("ajaxStart");//觸發事件
問題2:trigger一開始就判斷傳入的type引數是否有type屬性,那麼我們就先來看一看jQuery.Event物件?
分開來看一看jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !(this instanceof jQuery.Event) ) {//作用域安全的建構函式,所以即使沒有new jQuery.Event也是相當於用new來構造物件的! return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) {//可以傳入一個Event物件,也可以只是傳入一個字串如new jQuery.Event("click"); this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && // Support: IE < 9, Android < 4.0 src.returnValue === false ? returnTrue : returnFalse; // Event type } else { this.type = src; } // Put explicitly provided properties onto the event object if ( props ) {//第二個引數會被直接封裝到this物件上面 jQuery.extend( this, props ); } // Create a timestamp if incoming event doesn't have one this.timeStamp = src && src.timeStamp || jQuery.now(); // Mark it as fixed this[ jQuery.expando ] = true;//事件物件也有鑰匙 };
表示我們可以傳入一個event物件,這時候該event物件會用originalEvent屬性儲存;也可以傳入一個字串,該字串表示事件的型別。if ( src && src.type ) {//傳入event物件 this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && // Support: IE < 9, Android < 4.0 src.returnValue === false ? returnTrue : returnFalse; // Event type } else {//傳入字串 this.type = src; }
if ( props ) {
jQuery.extend( this, props );
}
傳入的第二個引數會直接封裝到jQuery.Event物件上面,作為事件的多餘的引數! this.timeStamp = src && src.timeStamp || jQuery.now();
// Mark it as fixed
this[ jQuery.expando ] = true;
構建的event物件有倉庫和timeStamp屬性!我們再來看看jQuery.Event的原型物件
正常情況下不阻止預設行為,不阻止冒泡
isDefaultPrevented: returnFalse,//預設行為不阻止
isPropagationStopped: returnFalse,//不阻止冒泡
isImmediatePropagationStopped: returnFalse,//不阻止冒泡
preventDefault呼叫的時候其實還是呼叫js物件的event,而不是jQuery.Event物件的方法,所以需要從jQuery.Event物件中剝離出來js的event物件,也就是通過originalEvent
preventDefault: function() {
var e = this.originalEvent;
this.isDefaultPrevented = returnTrue;
//呼叫了preventDefaule就會把修改isDefaultPrevented為true
if ( !e ) {
return;
}
// If preventDefault exists, run it on the original event
if ( e.preventDefault ) {
e.preventDefault();
// Support: IE
// Otherwise set the returnValue property of the original event to false
} else {
e.returnValue = false;
}
}
stopPropagation還是通過原生js物件的來進行,所以也要通過jQuery的event物件獲取到js物件的event物件
stopPropagation: function() {
var e = this.originalEvent;
this.isPropagationStopped = returnTrue;//修改為已經阻止冒泡了
if ( !e ) {
return;
}
// If stopPropagation exists, run it on the original event
if ( e.stopPropagation ) {
e.stopPropagation();
}
// Support: IE
// Set the cancelBubble property of the original event to true
e.cancelBubble = true;
}
stopImmediatePropagation也是通過原生JS物件來阻止冒泡的
stopImmediatePropagation: function() {
var e = this.originalEvent;
this.isImmediatePropagationStopped = returnTrue;//阻止冒泡行為
if ( e && e.stopImmediatePropagation ) {
e.stopImmediatePropagation();
}
this.stopPropagation();
}
我們來看一看stopPropagation和stopImmediatePropagation的區別:
$("body").on("click",function(e)
{
console.log("body");
});
$("#parent").on("click",function(e)
{
console.log("click1");
});
$("#parent").on("click",function(e)
{
console.log("click2");
});
預設情況下點選parent會呼叫所有的回撥函式!我們呼叫一下stopPropagation:
$("body").on("click",function(e)
{
console.log("body");
});
$("#parent").on("click",function(e)
{
console.log("click1");
e.stopPropagation();//防止冒泡,但是兩次click還是都會呼叫
});
$("#parent").on("click",function(e)
{
console.log("click2");
});
這時候會呼叫parent元素的兩次click事件,但是body不會捕獲到事件,因為已經阻止冒泡了。
$("body").on("click",function(e)
{
console.log("body");
});
$("#parent").on("click",function(e)
{
console.log("click1");
e.stopImmediatePropagation();//防止冒泡,同時後面所有的click事件都不會被呼叫!
});
$("#parent").on("click",function(e)
{
console.log("click2");
});
這時候只會呼叫parent元素的一次click事件,和stopPropagation一樣,body也不會捕獲到事件!注意:前面繫結的事件會先呼叫,所以他呼叫任何一個方法都會阻止冒泡,但是stopImmediatePropataion會取消甚至後面本元素的click事件!問題3:jQuery.event.trigger步驟是怎麼樣的?
第一步:看event物件是否有type屬性和namespace屬性,因為如果有namespace那麼只能觸發相同namespace屬性的事件
eventPath = [ elem || document ],
//如果傳入的event有type屬性,那麼獲取該type屬性
type = hasOwn.call( event, "type" ) ? event.type : event,
//如果有namespaces就獲取namespaces屬性!
namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
第二步:文字註釋節點直接返回,同時focus和blur採用了代理,所以不直接呼叫
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
return;
}
// focus/blur morphs to focusin/out; ensure we're not firing them right now
// rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
return;
}
第三步:根據使用者傳入的事件型別(注意,該事件型別可能含有名稱空間)構建自己的名稱空間和jQuery.Event物件,以便觸發事件
//如果type含有點好,那麼第一項就是事件型別,如click,其餘為名稱空間!
if ( type.indexOf(".") >= 0 ) {
// Namespaced trigger; create a regexp to match event type in handle()
namespaces = type.split(".");
type = namespaces.shift();
namespaces.sort();
}
//如果type沒有冒號,那麼ontype就是在前面加上on
ontype = type.indexOf(":") < 0 && "on" + type;
// Caller can pass in a jQuery.Event object, Object, or just an event type string
//如果是jQuery.Event物件,那麼是有倉庫的,所以直接返回。如果不是jQuery.Event物件
//那麼我們重新包裝他為jQuery.Event物件,同時使得該物件具有原始event物件的所有資訊!
event = event[ jQuery.expando ] ?
event :
new jQuery.Event( type, typeof event === "object" && event );
// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
event.isTrigger = onlyHandlers ? 2 : 3;
//為event設定namespace屬性,其中namespace屬性就是使用者自己傳入的namespace屬性,
//namespace_re屬性是一個正則表示式,這個正則表示式通過使用者自己傳入的namespace來構建!
event.namespace = namespaces.join(".");
event.namespace_re = event.namespace ?
new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
null;
// Clean up the event in case it is being reused
//把event的result屬性設定為undeined,以便回收使用!
event.result = undefined;
//設定target物件,如果沒有target,那麼表示target就是傳入的引數,記住:這裡是trigger不是使用者行為觸發的!
if ( !event.target ) {
event.target = elem;
}
第四步:如果使用者呼叫trigger傳入了資料,那麼結果資料就是[event,data]型別。同時,如果是特殊型別,同時呼叫特殊型別的trigger結果是false,那麼什麼也不做
// Clone any incoming data and prepend the event, creating the handler arg list
//如果沒有傳入data,那麼data就是event物件,makeArray方法如果傳入第二個引數表示把第一個data內容放入到event中
//所以如果傳入了data那麼data就是[event,data]!
data = data == null ?
[ event ] :
jQuery.makeArray( data, [ event ] );
// Allow special events to draw outside the lines
special = jQuery.event.special[ type ] || {};
//呼叫special自己有的trigger方法!
if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
return;
}
第五步:獲取到當前DOM應該冒泡的路徑,儲存到eventPath陣列中,而且採用的是push方法,所以像document等元素是在陣列最後的!
// Determine event propagation path in advance, per W3C events spec (#9951)
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
bubbleType = special.delegateType || type;
// rfocusMorph = /^(?:focusinfocus|focusoutblur)$/
//也就是說bubbleType和type連結起來不是這種型別,那麼獲取父元素.因為focus和blur不冒泡!
if ( !rfocusMorph.test( bubbleType + type ) ) {
cur = cur.parentNode;
}
for ( ; cur; cur = cur.parentNode ) {
// eventPath = [ elem || document ]預設是document
//eventPath裡面放入的是當前物件的所有父元素
eventPath.push( cur );
tmp = cur;
}
//給eventPath裡面新增window的defaultView或者parentWindow
// Only add window if we got to document (e.g., not plain obj or detached DOM)
if ( tmp === (elem.ownerDocument || document) ) {
eventPath.push( tmp.defaultView || tmp.parentWindow || window );
}
}
第六步:第五步獲取到了DOM應該冒泡的路徑儲存到了eventPath陣列中,這裡就對這個eventPath中的陣列進行呼叫
i = 0;
//獲取傳入的物件elem的每一個父元素,同時這個event沒有停止冒泡!
while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
//如果event.type大於1那麼就是bubbleType否則是bindType或者type本身
event.type = i > 1 ?
bubbleType :
special.bindType || type;
// jQuery handler
//不斷獲取當前元素和父元素的events物件,然後獲取該物件的type型別物件,同時獲取該物件的handle就是要執行的函式物件
handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
//如果回撥函式存在,那麼呼叫該函式,傳入的資料是data,也就是傳入trigger函式傳入的引數物件
if ( handle ) {
handle.apply( cur, data );
}
//本地handler物件,獲取該物件的on事件
// Native handler
handle = ontype && cur[ ontype ];
//如果on事件存在,同時該物件能夠儲存資料也就是Element物件
if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
//為event設定result值為handle呼叫後的結果
event.result = handle.apply( cur, data );
//如果返回是false,那麼就直接停止冒泡
if ( event.result === false ) {
event.preventDefault();
}
}
}
JS例項:
$("#n1").on("click",function(){console.log("n1 ")});
$("#n2").on("click",function(){console.log("n2 ")});
$("#n3").on("click",function(){console.log("n3 ")});
console.log($("#n3")[0].ownerDocument.defaultView);//Chrome是defaultView獲取window物件,其它瀏覽器用parentWindow獲取
window.onclick=function(e)
{
console.log("window");
}
HTML部分
<div id="n1">
<div id="n2">
<input type="checkbox" id="n3" href="www.baidu.com" style="display:inline-block;height:100px;width:100px;border:1px solid red;">
</div>
</div>
這時候點選checkbox你會發現,最先呼叫的n3,然後是n2,最後是n1和window,這就是上面為什麼會先儲存target,然後才儲存父元素的原因,這也是事件冒泡時候應該有的順序;至於為什麼也會觸發window物件的事件,是因為在eventPath中儲存了elem.ownnerDocument.defaultView或者parentWindow來儲存window物件!總之:呼叫的時候是獲取eventPath中每一個DOM的特定型別的事件集合,同時呼叫該DOM的通用回撥函式,通用回撥函式上下文為該DOM,該函式傳入的唯一的引數是上面第四步構建的data物件!同時,如果呼叫的該型別事件還有ontype事件,那麼ontype事件也需要呼叫,呼叫函式上下文是當前DOM,引數是第四步構建的資料物件,但是如果ontype返回了false,那麼就不會向父元素冒泡了!總之,按照jQuery.event.trigger來觸發某一類特殊的事件的話,他會觸發該DOM所有父元素的同類的事件,包括on型別事件和通過其它方式繫結的事件!
最後一步:如果呼叫trigger方法,那麼我需要觸發物件的預設行為,這是為了和使用者觸發事件行為保持一致必須做的,如點選checkbox時候必須選中等
//設定type的值
event.type = type;
// If nobody prevented the default action, do it now
//如果不是一次呼叫,同時也沒有阻止預設行為,那麼我們還需要觸發預設行為,這一點很重要!
if ( !onlyHandlers && !event.isDefaultPrevented() ) {
if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
jQuery.acceptData( elem ) ) {
// Call a native DOM method on the target with the same name name as the event.
// Can't use an .isFunction() check here because IE6/7 fails that test.
// Don't do default actions on window, that's where global variables be (#6170)
if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
//獲取ontype事件控制代碼,從而使得在已經執行了type方法後不執行ontype事件!
// Don't re-trigger an onFOO event when we call its FOO() method
tmp = elem[ ontype ];
//將ontype控制代碼設定為null
if ( tmp ) {
elem[ ontype ] = null;
}
// Prevent re-triggering of the same event, since we already bubbled it above
//防止再次執行同樣的事件,因為已經冒泡了!
jQuery.event.triggered = type;
try {
//執行type函式!
elem[ type ]();
} catch ( e ) {
// IE<9 dies on focus/blur to hidden element (#1486,#12518)
// only reproducible on winXP IE8 native, not IE9 in IE8 mode
}
//將trigger設定為null
jQuery.event.triggered = undefined;
if ( tmp ) {
elem[ ontype ] = tmp;
}
}
}
}
問題4:我們這裡採用了jQuery.event.trigger來呼叫函式的,那麼我們回撥函式中的上下文和引數是什麼?
jQuery.event.trigger呼叫
trigger: function( event, data, elem, onlyHandlers ) //trigger第二個引數是資料,不過我們會把data最終轉化為[event,data]型別
通過trigger觸發事件的時候引數和上下文通過下面的程式碼來觸發:
event.result = handle.apply( cur, data );//這裡的data格式為[event,data]型別,cur是事件流當前所在的元素!
例子:HTML部分如上
$("#n1").on("click",function(e,args){console.log(e);console.log(args);});//this就是當前DOM!
jQuery.event.trigger("click",{name:"qinliang"},$("#n1")[0]);//這時候你就知道,其實通過trigger呼叫的時候第一個引數是jQuery.Event物件,args是引數!
問題5:上面最後一步還是沒懂,什麼叫阻止元素的預設行為?
還是上面的HTML結構
$("#n1").on("click",function(e,args){console.log(e);});
$("#n1")[0].onclick=function(e)//通過這種方式新增事件是JS原生新增事件的方式,不是通過新增到DOM的events域上面的,通過elem[ontype]就可以訪問的!
{
console.log("on click");
}
jQuery.event.trigger("click",{name:"qinliang"},$("#n1")[0]);
注意:通過onclick直接新增和通過on或者bind新增有本質的區別,後者是新增到DOM的events域下面,但是前者直接通過elem[ontype]就能夠訪問了!所以,最後一步就是針對這種新增事件的情況的,tmp = elem[ ontype ]就是獲取ontype事件型別,這種型別直接新增到DOM的onclick屬性下,所以後面要執行元素的預設行為!如何阻止該事件的預設行為?
$("#n1").on("click",function(e,args){e.preventDefault();});//阻止預設行為
$("#n1")[0].onclick=function(e)//該例子不管你怎麼點選,checkbox都不會被選中!
{
console.log("on click");
}
jQuery.event.trigger("click",{name:"qinliang"},$("#n1")[0]);
因為,通過原始碼我們可以看出:這種預設行為是最後才被執行的,所以如果前面通過on或者bind等方式繫結的事件呼叫了preventDefault,那麼預設行為就不會被執行了。問題6:trigger呼叫能夠阻止預設行為,那麼trigger阻止了預設行為之後,使用者手動觸發事件是否能夠再次觸發事件?
解答:可以
$("#n1").on("click",function(e,args){e.preventDefault()});
$("#n1")[0].onclick=function(e)
{
console.log("on click");
}
jQuery.event.trigger("click",{name:"qinliang"},$("#n1")[0]);
這種方式,n1上面的click和onclick都會觸發,因為上面已經說了:一步一步往上執行,每一步都會執行on繫結的和ontype繫結的所有的事件,但是隻要有一個呼叫了preventDefault那麼最後一步壓根不會執行,所以預設行為就不存在了!所以,這一步呼叫trigger時候兩者都會觸發,但是當我們以後點選的時候兩者還是會觸發,只是預設行為不會觸發了!問題7:jQuery.event.trigger是如何阻止預設行為的?
if ( !onlyHandlers && !event.isDefaultPrevented() ) {
if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
jQuery.acceptData( elem ) ) {
// Call a native DOM method on the target with the same name name as the event.
// Can't use an .isFunction() check here because IE6/7 fails that test.
// Don't do default actions on window, that's where global variables be (#6170)
if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
//獲取ontype事件控制代碼,從而使得在已經執行了type方法後不執行ontype事件!
// Don't re-trigger an onFOO event when we call its FOO() method
tmp = elem[ ontype ];
//將ontype控制代碼設定為null
if ( tmp ) {
elem[ ontype ] = null;
}
// Prevent re-triggering of the same event, since we already bubbled it above
//防止再次執行同樣的事件,因為已經冒泡了!
jQuery.event.triggered = type;
try {
//執行type函式!
elem[ type ]();
} catch ( e ) {
// IE<9 dies on focus/blur to hidden element (#1486,#12518)
// only reproducible on winXP IE8 native, not IE9 in IE8 mode
}
//將trigger設定為null
jQuery.event.triggered = undefined;
if ( tmp ) {
elem[ ontype ] = tmp;
}
}
}
如果使用者沒有呼叫preventDefault了,那麼我們就會呼叫elem[type],那麼elem[type]這個函式是什麼呢,請看該圖。這也就是說,這個預設行為是在最後一步被執行的。開始的時候是從target一直到父元素逐層呼叫他們通過on方法或者ontype方法新增的回撥函式。最後一步才開始呼叫預設的函式!問題7:jQuery.event.triggered = type;在幹嘛?
// Prevent re-triggering of the same event, since we already bubbled it above
//防止再次執行同樣的事件,因為已經冒泡了!
jQuery.event.triggered = type;
try {
//執行type函式!
elem[ type ]();
} catch ( e ) {
// IE<9 dies on focus/blur to hidden element (#1486,#12518)
// only reproducible on winXP IE8 native, not IE9 in IE8 mode
}
//將trigger設定為null
jQuery.event.triggered = undefined;
解答:在預設行為函式執行之前其值為type,預設行為執行過了,其值為undefined!問題8:如果有些事件如blur,focus無法冒泡怎麼辦,這時候父元素應該觸發什麼型別事件?
$("#n1").on("focusin",function(e){console.log("n1 on focus");});
$("#n3").on("focus",function(e){console.log("on focus");});
jQuery.event.trigger("focus",{name:"qinliang"},$("#n3")[0]);
這時候你就會明白n3冒泡的應該是focusin事件,如果把繫結給n1的focusin修改為focus,那麼n1上面的事件不會執行!參見部落格問題9:如何呼叫自定義事件?
$("#n3").on("qinliang",function(e)
{
console.log("qinliang invoked!");
});
//自定義事件的執行,on方法把事件新增到events域下面,同時trigger把events域下面的同名事件
//拿出來執行!
jQuery.event.trigger("qinliang",{name:"qinliang"},$("#n3")[0]);
問題10:jQuery.event.trigger原始碼?
trigger: function( event, data, elem, onlyHandlers ) {
var handle, ontype, cur,
bubbleType, special, tmp, i,
eventPath = [ elem || document ],
type = hasOwn.call( event, "type" ) ? event.type : event,
namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
cur = tmp = elem = elem || document;
// Don't do events on text and comment nodes
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
return;
}
// focus/blur morphs to focusin/out; ensure we're not firing them right now
if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
return;
}
if ( type.indexOf(".") >= 0 ) {
// Namespaced trigger; create a regexp to match event type in handle()
namespaces = type.split(".");
type = namespaces.shift();
namespaces.sort();
}
ontype = type.indexOf(":") < 0 && "on" + type;
// Caller can pass in a jQuery.Event object, Object, or just an event type string
event = event[ jQuery.expando ] ?
event :
new jQuery.Event( type, typeof event === "object" && event );
// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
event.isTrigger = onlyHandlers ? 2 : 3;
event.namespace = namespaces.join(".");
event.namespace_re = event.namespace ?
new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
null;
// Clean up the event in case it is being reused
event.result = undefined;
if ( !event.target ) {
event.target = elem;
}
// Clone any incoming data and prepend the event, creating the handler arg list
data = data == null ?
[ event ] :
jQuery.makeArray( data, [ event ] );
// Allow special events to draw outside the lines
special = jQuery.event.special[ type ] || {};
if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
return;
}
// Determine event propagation path in advance, per W3C events spec (#9951)
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
bubbleType = special.delegateType || type;
if ( !rfocusMorph.test( bubbleType + type ) ) {
cur = cur.parentNode;
}
for ( ; cur; cur = cur.parentNode ) {
eventPath.push( cur );
tmp = cur;
}
// Only add window if we got to document (e.g., not plain obj or detached DOM)
if ( tmp === (elem.ownerDocument || document) ) {
eventPath.push( tmp.defaultView || tmp.parentWindow || window );
}
}
// Fire handlers on the event path
i = 0;
while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
event.type = i > 1 ?
bubbleType :
special.bindType || type;
// jQuery handler
handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
if ( handle ) {
handle.apply( cur, data );
}
// Native handler
handle = ontype && cur[ ontype ];
if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
event.result = handle.apply( cur, data );
if ( event.result === false ) {
event.preventDefault();
}
}
}
event.type = type;
// If nobody prevented the default action, do it now
if ( !onlyHandlers && !event.isDefaultPrevented() ) {
if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
jQuery.acceptData( elem ) ) {
// Call a native DOM method on the target with the same name name as the event.
// Can't use an .isFunction() check here because IE6/7 fails that test.
// Don't do default actions on window, that's where global variables be (#6170)
if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
// Don't re-trigger an onFOO event when we call its FOO() method
tmp = elem[ ontype ];
if ( tmp ) {
elem[ ontype ] = null;
}
// Prevent re-triggering of the same event, since we already bubbled it above
jQuery.event.triggered = type;
try {
console.log(elem[type]);
elem[ type ]();
} catch ( e ) {
// IE<9 dies on focus/blur to hidden element (#1486,#12518)
// only reproducible on winXP IE8 native, not IE9 in IE8 mode
}
jQuery.event.triggered = undefined;
if ( tmp ) {
elem[ ontype ] = tmp;
}
}
}
}
return event.result;
}
注意:最後的elem[type]如果你去alert出來會發現結果是function click(){[native code]}也就是說這裡是為target物件呼叫預設方法,如果是checkbox就是表示選中,如果是超連結預設行為就是表示開啟等!但是對於click方法來說,根據原始碼,如果你是triggerClick,那麼根本不會開啟超連結,其它元素預設事件會觸發,唯獨超連結的click不會!
上面你必須要弄清楚:
function A( event,data ){
alert( 'A' +data.name);
}
function B( event,data ){
alert( 'B' +data.name);
}
function C( event,data ){
alert( 'C' +data.name);
}
var $btn1 = $("#btn1");
// 為btn1元素的click事件繫結事件處理函式
$btn1.bind( "click.foo.bar", A );
$btn1.bind( "click.test.foo", B );
$btn1.bind( "click.test", C );
// 觸發btn1的包含名稱空間test的click事件
$btn1.trigger("click.foo",{name:"xxx"},$btn1); // 觸發B、C
(1)為什麼trigger一次會同時彈出B和C呢?對同一個物件來說,bind方法呼叫了3次,那麼在events裡面放入了3個handleObj,但是每次都是對應的同一個handle,也就是這個元素的handle。當你真正呼叫trigger的時候,其實就是拿著這個函式去執行!這個函式內部呼叫了dispatch方法,dispatch方法會通過handlers方法判定出需要執行多次,因為handleObj有多個物件都能夠滿足執行要求!所以說通過點選執行和通過trigger執行是一樣的,都是呼叫handle函式!
(2)那麼當呼叫trigger的時候如何保證相應的namespace下的所有函式都會執行呢?這時候trigger中的namespace_re程式碼就起作用了,他會根據自己的名稱空間建立相應的正則表示式,然後儲存到event物件中!同時event會傳遞到handle方法中,然後傳遞到dispatch中,該方法中有一個判斷 if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) )這樣就能夠保證通過on方法新增的handleObj如果滿足這個正則表示式就能夠執行!如第一個handleObj是"foo.bar",第二個是"test.foo",第三個是"test",於是用trigger的正則表示式去匹配,最後就會執行B和C!
(3)我們得出結論,通過點選觸發事件和通過trigger觸發事件本質上沒有任何區別(除了click作用於a),都是呼叫相應元素的handle方法!該冒泡的時候也是會冒泡的!回撥的時候會給相應的繫結的引數傳遞event物件和data物件!
(4)為什麼我說上面trigger方法該冒泡的時候也會冒泡呢?如果子元素呼叫trigger("click.test")那麼所有的父元素的"click"為什麼不會執行呢?
$(document.getElementById("container")).on("click",function(){alert("invoked container!")})
$(document.getElementById("content")).on("click",function(event){alert("invoked content!"+event.type)})
//拿著上面所有的click事件型別
$(document.getElementById("content")).trigger("click.test");
我們上面說過通過trigger和手動呼叫的結果是一樣的,那麼邏輯肯定也是一樣的!當trigger("click.test")的時候,那麼就會拿著content目標元素和父元素的所有的引用儲存到陣列中間,然後不斷進行冒泡,當在target階段的時候發現了自己的click事件,該不該觸發呢?我們上面說過在觸發事件的時候會用namespace_re儲存觸發時候的正則表示式,這裡是"test",然後拿著這個正則表示式和所有的目標物件和父元素物件的正則表示式進行對比,也就是用trigger時候的正則表示式判斷繫結事件時候的正則表示式,最後就是/test/.test("")都是false結果就是目標物件和父元素的物件的所有的同類事件都不會執行!但是如果把on中的字串修改為"click.test"那麼就都會觸發!(注意:正則表示式仔細分析你會發現,如果繫結事件用了"click.foo.test"觸發時候用"click.test.foo"也會觸發!因為在jQuery.event.add裡面和jQuery.event.trigger裡面都對名稱空間陣列進行了sort了!)如果叫你設計一個觸發一個物件的事件,你會怎麼設計?
(1)獲取該元素以及該元素所有的父元素,至於為什麼用父元素是為了用於冒泡的!得到一個DOM集合!
(2)迴圈遍歷DOM集合,獲取每一個元素上面相應的事件,如果i<1獲取繫結事件,否則獲取冒泡事件!並且呼叫事件!
(3)迴圈結束以後,對target物件呼叫原生方法,使得他能夠完成瀏覽器預設的動作,如checkbox就是選中,a就是開啟連結!