前言:
這篇依舊長,請耐心看下去。javascript
1、事件委託
DOM有個事件流特性,因此觸發DOM節點的時候,會經歷3個階段:
(1)階段一:Capturing 事件捕獲(從祖到目標)
在事件
自上(document->html->body->xxx)而下到達目標節點的過程當中,瀏覽器會檢測 針對該事件的 監聽器(用來捕獲事件),並運行捕獲事件的監聽器。html
(2)階段二:Target 目標
瀏覽器找到監聽器後,就運行該監聽器java
(3)階段三:Bubbling 冒泡(目標到祖)
在事件
自下而上(document->html->body->xxx)到達目標節點的過程當中,瀏覽器會檢測不是 針對該事件的 監聽器(用來捕獲事件),並運行非捕獲事件的監聽器。node
2、$()
.click()
做用:
爲目標元素綁定點擊事件api
源碼:數組
//這種寫法還第一次見,將全部鼠標事件寫成字符串再換成數組
//再一一綁定到DOM節點上去
//源碼10969行
jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup contextmenu" ).split( " " ),
function( i, name ) {
//事件綁定
// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
//若是有參數的話,就用jQuery的on綁定
this.on( name, null, data, fn ) :
//不然使用trigger
this.trigger( name );
};
} );
複製代碼
解析:
能夠看到,jQuery 將全部的鼠標事件都一一列舉了出來,並經過jQuery.fn[ name ] = function( data, fn ) { xxx }
瀏覽器
若是有參數,則是綁定事件,調用 on() 方法;
沒有參數,則是調用事件,調用 trigger() 方法( trigger() 放到下篇講 )app
3、$()
.on()
做用:
在被選元素及子元素上添加一個或多個事件處理程序dom
源碼:ide
//綁定事件的方法
//源碼5812行
jQuery.fn.extend( {
//在被選元素及子元素上添加一個或多個事件處理程序
//$().on('click',function()=<{})
//源碼5817行
on: function( types, selector, data, fn ) {
return on( this, types, selector, data, fn );
},
//xxx
//xxx
})
複製代碼
最終調用的是 jQuery.on() 方法:
//綁定事件的on方法
//源碼5143行
//目標元素,類型(click,mouseenter,focusin,xxx),回調函數function(){xxx}
function on( elem, types, selector, data, fn, one ) {
var origFn, type;
//這邊能夠不看
// Types can be a map of types/handlers
if ( typeof types === "object" ) {
// ( types-Object, selector, data )
if ( typeof selector !== "string" ) {
// ( types-Object, data )
data = data || selector;
selector = undefined;
}
for ( type in types ) {
on( elem, type, selector, data, types[ type ], one );
}
return elem;
}
//直接調用$().on()的話會走這邊
if ( data == null && fn == null ) {
// ( types, fn )
//fn賦值爲selector,即function(){}
fn = selector;
//再將selector置爲undefined
//注意這個寫法,連等賦值
data = selector = undefined;
}
//調用像$().click()的話會走這邊
else if ( fn == null ) {
if ( typeof selector === "string" ) {
// ( types, selector, fn )
fn = data;
data = undefined;
} else {
// ( types, data, fn )
fn = data;
data = selector;
selector = undefined;
}
}
if ( fn === false ) {
fn = returnFalse;
} else if ( !fn ) {
return elem;
}
//one()走這裏
if ( one === 1 ) {
//將fn賦給origFn後,再定義fn
origFn = fn;
fn = function( event ) {
//將綁定給目標元素的事件傳給fn,
//並經過$().off()卸載掉
// Can use an empty set, since event contains the info
jQuery().off( event );
//在origFn運行一次的基礎上,讓origFn調用fn方法,arguments即event
return origFn.apply( this, arguments );
};
//讓fn和origFn使用相同的guid,這樣就能移除origFn方法
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
return elem.each( function() {
//最終調動$.event.add方法
jQuery.event.add( this, types, fn, data, selector );
} );
}
複製代碼
解析:
能夠看到,因爲將 bind()、live() 和 delegate() 都合併進 on() 後,on() 裏面的狀況挺複雜的, data、selector、fn 相互賦值。
注意下 if ( one === 1 )
這種狀況,是 $().one()
在on()
裏的具體實現,即調用一次on()
後,就執行jQuery().off( event )
,卸載事件。
該方法最終調用 jQuery.event.add( ) 方法
4、jQuery.event.add( )
做用:
爲目標元素添加事件
源碼:
//源碼5235行
/*
* Helper functions for managing events -- not part of the public interface.
* Props to Dean Edwards' addEvent library for many of the ideas.
*/
jQuery.event = {
global: {},
//源碼5241行
//this, types, fn, data, selector
add: function( elem, types, handler, data, selector ) {
var handleObjIn, eventHandle, tmp,
events, t, handleObj,
special, handlers, type, namespaces, origType,
//elemData正是目標元素jQuery中的id屬性
//初始值是{}
elemData = dataPriv.get( elem );
// Don't attach events to noData or text/comment nodes (but allow plain objects)
if ( !elemData ) {
return;
}
//調用者能夠傳入一個自定義數據對象來代替處理程序
// Caller can pass in an object of custom data in lieu of the handler
if ( handler.handler ) {
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
//確保不正確的選擇器會拋出異常
// Ensure that invalid selectors throw exceptions at attach time
// Evaluate against documentElement in case elem is a non-element node (e.g., document)
if ( selector ) {
jQuery.find.matchesSelector( documentElement, selector );
}
//確保handler有惟一的id
// Make sure that the handler has a unique ID, used to find/remove it later
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
//若是事件處理沒有,則置爲空對象
// Init the element's event structure and main handler, if this is the first
//在這裏,就應經給events賦值了,
// 注意這種寫法:賦值的同時,判斷
if ( !( events = elemData.events ) ) {
events = elemData.events = {};
}
if ( !( eventHandle = elemData.handle ) ) {
eventHandle = elemData.handle = function( e ) {
//當在一個頁面卸載後調用事件時,取消jQuery.event.trigger()的第二個事件
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
//jQuery.event.triggered: undefined
//e.type: click/mouseout
return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
//讓elem調用jQuery.event.dispatch方法,參數是arguments
jQuery.event.dispatch.apply( elem, arguments ) : undefined;
};
}
//經過空格將多個events分開,通常爲一個,如click
// Handle multiple events separated by a space
types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
t = types.length;
while ( t-- ) {
tmp = rtypenamespace.exec( types[ t ] ) || [];
//click
type = origType = tmp[ 1 ];
//""
namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
// There *must* be a type, no attaching namespace-only handlers
if ( !type ) {
continue;
}
//若是event改變了它本身的type,就使用特殊的event handlers
// If event changes its type, use the special event handlers for the changed type
special = jQuery.event.special[ type ] || {};
//若是選擇器已定義,肯定一個特殊event api的type
//不然使用默認type
// If selector defined, determine special event api type, otherwise given type
type = ( selector ? special.delegateType : special.bindType ) || type;
//不明白爲何在上面要先寫一遍
// Update special based on newly reset type
special = jQuery.event.special[ type ] || {};
//handleObj會傳遞給全部的event handlers
// handleObj is passed to all event handlers
handleObj = jQuery.extend( {
type: type,
origType: origType,
data: data,
handler: handler,
guid: handler.guid,
selector: selector,
needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
namespace: namespaces.join( "." )
}, handleObjIn );
//第一次綁定事件,走這裏
// Init the event handler queue if we're the first
if ( !( handlers = events[ type ] ) ) {
handlers = events[ type ] = [];
handlers.delegateCount = 0;
// Only use addEventListener if the special events handler returns false
if ( !special.setup ||
special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
//目標元素有addEventListener的話,調用綁定click事件
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle );
}
}
}
//special的add/handleObj.handler.guidd的初始化處理
if ( special.add ) {
special.add.call( elem, handleObj );
if ( !handleObj.handler.guid ) {
handleObj.handler.guid = handler.guid;
}
}
// Add to the element's handler list, delegates in front
if ( selector ) {
handlers.splice( handlers.delegateCount++, 0, handleObj );
} else {
handlers.push( handleObj );
}
//一旦有綁定事件,全局通知
// Keep track of which events have ever been used, for event optimization
jQuery.event.global[ type ] = true;
}
},
...
...
}
複製代碼
解析:
能夠看到,不少的 if 判斷,都是在初始化對象,最後經過 while 循環,調用目標元素的 addEventListener 事件,也就是說,click()/on() 的本質是 element.addEventListener() 事件,前面一系列的鋪墊,都是在爲目標 jQuery 對象添加必要的屬性。
注意寫法 if ( !( events = elemData.events ) )
,在賦值的同時,判斷條件
(1)dataPriv
//取惟一id
//源碼4361行
var dataPriv = new Data();
複製代碼
在 jQuery 對象中,有惟一id的屬性
$("#one")
複製代碼
elemData = dataPriv.get( elem )
複製代碼
① Data()
//目標元素的jQuery id
//源碼4209行
function Data() {
this.expando = jQuery.expando + Data.uid++;
}
複製代碼
② jQuery.expando
jQuery.extend( {
//至關於jQuery爲每個元素取惟一的id
///\D/g : 去掉非數字的字符
// Unique for each copy of jQuery on the page
//源碼360行
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
...
...
})
複製代碼
③ Math.random()
僞隨機,到小數點後16位
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
複製代碼
能夠看到 jQuery 的 id 是由 jQuery + 版本號+ Math.random() 生成的
關於 Math.random() 是如何生成僞隨機數的請看:www.zhihu.com/question/22…
(2)rtypenamespace
var
rkeyEvent = /^key/,
rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
//事件類型的命名空間
//舉例:var arr1 = "click.aaa.bbb".match(rtypenamespace);
//console.log(arr1);//["click.aaa.bbb", "click", "aaa.bbb", index: 0, input: "click.aaa.bbb"]
//源碼5131行
rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
複製代碼
綜上,綁定事件的本質即調用element.addEventListener()
方法,但 jQuery 有太多的狀況須要考慮了。
(完)