jQuery源码-事件绑定函数bind/delegate/click...

 前言:我用的jQuery版本为v2.0.1,jQuery绑定事件的接口有bind/delegate/click等其他方法,但是他们最终调用的还是this.on作为入口

但是jQuery提供两种绑定机制

1.普通事件绑定

2.委托绑定事件

这两中的区别有很大的不同,只是在作用上。委托绑定事件机制能给浏览器带来很大的性能优化。不过两种可以防止浏览器的内存泄露( 特别是在IE中)

今天我用click方法走下普通事件绑定的源码,大同小异

一:// 又是一种简写模式 循环生成事件函数
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {

// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {

return arguments.length > 0 ?
this.on( name, null, data, fn ) : // click null callback undefined
this.trigger( name );
};
});

1.jQuery用简写模式,使基本事件函数循环到jQuery..fn中,可以让外部调用 

二:// jquery on
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
// click null  callback undefined undefined
// click 'a' callback undefined undefined
var type, origFn;

// 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 ) {
this.on( type, selector, data, types[ type ], one );
}
return this;
}
// 参数调整
if ( data == null && fn == null ) {
// ( types, fn )
fn = selector;
data = selector = undefined;
} else if ( fn == null ) {
if ( typeof selector === "string" ) {
// ( types, selector, fn )
fn = data; // callback 
data = undefined; // undefined
} else {
// ( types, data, fn )
fn = data; // callback
data = selector; // null
selector = undefined; // undefined

}
}
if ( fn === false ) {
fn = returnFalse;
} else if ( !fn ) {
return this;
}

if ( one === 1 ) {
origFn = fn;
fn = function( event ) {
// Can use an empty set, since event contains the info
jQuery().off( event );
return origFn.apply( this, arguments );
};
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
return this.each( function() {
//  elem , click,callback,null,undefined
//  elem , click,callback,null,'a'
jQuery.event.add( this, types, fn, data, selector );
});
},

1.this.on()大部分工作主要是修复参数.

2.主要绑定工作在 jQuery.event.add( this, types, fn, data, selector );中

扫描二维码关注公众号,回复: 3573908 查看本文章

三://   elem,click,callback,null,undefined
//   elem,click,callback,null,'a'
add: function( elem, types, handler, data, selector ) {

var tmp, events, t, handleObjIn,
special, eventHandle, handleObj,
handlers, type, namespaces, origType,
elemData = jQuery._data( elem ); // {} 空对象 == jQuery.cache[id]

// 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;
}

// Make sure that the handler has a unique ID, used to find/remove it later
if ( !handler.guid ) {
handler.guid = jQuery.guid++; // 2
}

// Init the element's event structure and main handler, if this is the first

if ( !(events = elemData.events) ) {
events = elemData.events = {}; // 初始化为空对象
}

if ( !(eventHandle = elemData.handle) ) {
eventHandle = elemData.handle = function( e ) {
// 下面这段代码是点击真正事件的时候,执行代码
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : // elem event
undefined;
};

// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
eventHandle.elem = elem;
}

// Handle multiple events separated by a space
types = ( types || "" ).match( core_rnotwhite ) || [""]; // click

t = types.length;

while ( t-- ) {
// /^([^.]*)(?:\.(.+)|)$/
tmp = rtypenamespace.exec( types[t] ) || [];

type = origType = tmp[1]; // click
namespaces = ( tmp[2] || "" ).split( "." ).sort(); // [""]

// There *must* be a type, no attaching namespace-only handlers
if ( !type ) {
continue;
}


// If event changes its type, use the special event handlers for the changed type
special = jQuery.event.special[ type ] || {};

// If selector defined, determine special event api type, otherwise given type
type = ( selector ? special.delegateType : special.bindType ) || type; // clcik

// Update special based on newly reset type
special = jQuery.event.special[ type ] || {};


// 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;

// 任何事件都是用addEventListener/attachEvent绑定的
// Only use addEventListener/attachEvent if the special events handler returns false

if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
// Bind the global event handler to the element
if ( elem.addEventListener ) { // 非IE支持
// eventHandle 其实就是一个执行分派回调函数的一个入口

elem.addEventListener( type, eventHandle, false );


} else if ( elem.attachEvent ) { // IE支持
elem.attachEvent( "on" + type, eventHandle );
}
}
}

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 存放这一些回调函数
// handlers 引用 events[type] , events 引用 elemData.events , elemData 又是elem的事件缓存

// 0,0,obj
// 如果之前绑定了事件,则会在数组头部插入handleObj,不管用不用delegete方法,委托回调函数总是在普通回调函数后面执行
handlers.splice( handlers.delegateCount++, 0, handleObj );



} else {
// 普通事件处理   将组装的回调函数push进handlers  = events[type] = elemData.events[type]
handlers.push( handleObj );

}


// Keep track of which events have ever been used, for event optimization
jQuery.event.global[ type ] = true;
}


// Nullify elem to prevent memory leaks in IE
// 将回调函数压进回调函数列表后,在置空elem,释放内存.防止内存泄露
elem = null;
},


1.elemData = jQuery._data( elem ); // {} 空对象 == jQuery.cache[id]   

获取该元素在数据缓存仓库中的缓存数据,如果没有的话,就构造为空的对象

2.handler.guid = jQuery.guid++;  // Make sure that the handler has a unique ID, used to find/remove it later

给传进来的回调函数唯一一个ID,以备以后定位和删除

3.events = elemData.events = {}; // 初始化为空对象  如果elemData为空对象,则构造events属性 并且也为空对象

4.eventHandle = elemData.handle = function( e ) {
// 下面这段代码是点击真正事件的时候,执行代码
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : // elem event
undefined;
};

// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
eventHandle.elem = elem;

同理如果elemData为空对象,则构造handle属性 并且为一个函数,其中jQuery.event.dispatch.apply( eventHandle.elem, arguments ) 就是分派,也就是事件发生时真正执行的代码,稍后详细介绍内部

5.// Init the event handler queue if we're the first
if ( !(handlers = events[ type ]) ) {
handlers = events[ type ] = [];
handlers.delegateCount = 0;

// 任何事件都是用addEventListener/attachEvent绑定的
// Only use addEventListener/attachEvent if the special events handler returns false

if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
// Bind the global event handler to the element
if ( elem.addEventListener ) { // 非IE支持
// eventHandle 其实就是一个执行分派回调函数的一个入口

elem.addEventListener( type, eventHandle, false );


} else if ( elem.attachEvent ) { // IE支持
elem.attachEvent( "on" + type, eventHandle );
}
}
}

初始化基本的属性,和在DOM绑定全局函数,其中handlers.delegateCount = 0 表示普通事件绑定,否则为委托事件绑定

6.if ( selector ) {
// 委托事件处理 handlers 存放这一些回调函数
// handlers 引用 events[type] , events 引用 elemData.events , elemData 又是elem的事件缓存

// 0,0,obj
// 如果之前绑定了事件,则会在数组头部插入handleObj,不管用不用delegete方法,委托回调函数总是在普通回调函数后面执行
handlers.splice( handlers.delegateCount++, 0, handleObj );



} else {
// 普通事件处理   将组装的回调函数push进handlers  = events[type] = elemData.events[type]
handlers.push( handleObj );

}

2个分支都是将将组装的回调函数push进handlers  = events[type] = elemData.events[type] push进handlers中,只是过程不一样,

委托绑定,是数组头部插入,而普通事件是数组尾部插入,到这里基本的绑定初始化工作已经完成,事件数据已经初始化到数据仓库中了,并且为DOM绑定了全局回调函数,

接下来我们详细解析下全局回调函数

7.eventHandle = elemData.handle = function( e ) {
// 下面这段代码是点击真正事件的时候,执行代码
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : // elem event
undefined;
};

8.dispatch: function( event ) {

// Make a writable jQuery.Event from the native event object
// 让事件对象 在IE和其他浏览器兼容,并且附加一些自己的属性

event = jQuery.event.fix( event ); // 在fix函数中上下文this = jQuery.event

var i, ret, handleObj, matched, j,
handlerQueue = [],
args = core_slice.call( arguments ),
handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], // 通过获取elem的事件缓存,得到之前所保存的回调函数
special = jQuery.event.special[ event.type ] || {};

// Use the fix-ed jQuery.Event rather than the (read-only) native event
args[0] = event;

event.delegateTarget = this;

// Call the preDispatch hook for the mapped type, and let it bail if desired
// 分派之前的钩子

if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {

return;
}

// Determine handlers
handlerQueue = jQuery.event.handlers.call( this, event, handlers );

// Run delegates first; they may want to stop propagation beneath us
i = 0;
// 循环遍历执行handlerQueue的回调函数
// 如果matched存在,而且 停止冒泡状态机的标识为return false 
while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {

event.currentTarget = matched.elem; // 事件的目标对象

j = 0;
// 如果handleObj存在,而且 停止冒泡状态机的标识为return false 
while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {

// Triggered event must either 1) have no namespace, or
// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).

if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {

event.handleObj = handleObj;

event.data = handleObj.data;

// 在这里执行回调函数 将上下文替换成最初绑定的元素
// 特别注意这里的args,其实就是我们回调中的event实参,args将作为handleObj.handler(event){}中的event参数
ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
.apply( matched.elem, args );

if ( ret !== undefined ) {
if ( (event.result = ret) === false ) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
}


// Call the postDispatch hook for the mapped type
// 分派之后的钩子

if ( special.postDispatch ) {
special.postDispatch.call( this, event );
}

return event.result; // undefined
},

event = jQuery.event.fix( event ); // 在fix函数中上下文this = jQuery.event   

// Make a writable jQuery.Event from the native event object
// 让事件对象 在IE和其他浏览器兼容,并且附加一些自己的属性

9。fix: function( event ) {

// 如果该事件对象已经修复过,直接返回
if ( event[ jQuery.expando ] ) {
return event;
}


// Create a writable copy of the event object and normalize some properties
var i, prop, copy,
type = event.type, // click
originalEvent = event,
fixHook = this.fixHooks[ type ]; // undefined

//keyHooks.filter 和 mouseHooks.filter 两个方法分别用于修改键盘和鼠标事件的属性兼容性问题,用于统一接口。
// 时间处理的钩子机制
if ( !fixHook ) {
this.fixHooks[ type ] = fixHook =
rmouseEvent.test( type ) ? this.mouseHooks : // 鼠标事件特有属性
rkeyEvent.test( type ) ? this.keyHooks : // 按键事件特有属性
{};
}

// fixHook.props 鼠标/键盘事件特有的属性,将基础的属性和特有的属性合并,为了兼容
// 如果是鼠标事件,则将鼠标的属性兼容,同理按键事件也是一样
copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

// 构造新的自定义的event对象(原有的对象为新对象的一个属性)
event = new jQuery.Event( originalEvent );

i = copy.length; //25
// copy是修复属性的集合,如果原始对象和新构造的对象属性名相同,覆盖,如果不相同,这添加到新的对象中,兼容不同浏览器(IE)
while ( i-- ) {
prop = copy[ i ];
event[ prop ] = originalEvent[ prop ];
}

// Support: IE<9 IE9之前的事件的目标对象是用srcElement表示,而其他浏览器使用target表示
// 好像现在的IE浏览器基本和其他浏览器一致
// Fix target property (#1925)
if ( !event.target ) {
event.target = originalEvent.srcElement || document;
}

// Support: Chrome 23+, Safari?
// Target should not be a text node (#504, #13143)
if ( event.target.nodeType === 3 ) {
// 如果点击的是文本节点,则寻找他的父节点
event.target = event.target.parentNode;
}

// Support: IE<9
// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
event.metaKey = !!event.metaKey;

return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; // event
},

(1)//keyHooks.filter 和 mouseHooks.filter 两个方法分别用于修改键盘和鼠标事件的属性兼容性问题,用于统一接口。
// 时间处理的钩子机制
if ( !fixHook ) {
this.fixHooks[ type ] = fixHook =
rmouseEvent.test( type ) ? this.mouseHooks : // 鼠标事件特有属性
rkeyEvent.test( type ) ? this.keyHooks : // 按键事件特有属性
{};
}

(2)// fixHook.props 鼠标/键盘事件特有的属性,将基础的属性和特有的属性合并,为了兼容
// 如果是鼠标事件,则将鼠标的属性兼容,同理按键事件也是一样
copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

(3)// 构造新的自定义的event对象(原有的对象为新对象的一个属性)
event = new jQuery.Event( originalEvent );

(4)// copy是修复属性的集合,如果原始对象和新构造的对象属性名相同,覆盖,如果不相同,这添加到新的对象中,兼容不同浏览器(IE)
while ( i-- ) {
prop = copy[ i ];
event[ prop ] = originalEvent[ prop ];
}

// Support: IE<9 IE9之前的事件的目标对象是用srcElement表示,而其他浏览器使用target表示
// 好像现在的IE浏览器基本和其他浏览器一致
// Fix target property (#1925)
if ( !event.target ) {
event.target = originalEvent.srcElement || document;
}

// Support: Chrome 23+, Safari?
// Target should not be a text node (#504, #13143)
if ( event.target.nodeType === 3 ) {
// 如果点击的是文本节点,则寻找他的父节点
event.target = event.target.parentNode;
}

// Support: IE<9
// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
event.metaKey = !!event.metaKey;

(5)return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; // event  过滤 处理一些IE与其他浏览器的某些属性名

10.handlerQueue = jQuery.event.handlers.call( this, event, handlers );     将handlers处理,然后push进handlerQueue中


11.

// 在这里执行回调函数 将上下文替换成最初绑定的元素
// 特别注意这里的args,其实就是我们回调中的event实参,args将作为handleObj.handler(event){}中的event参数
ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
.apply( matched.elem, args );
(1)最后循环执行传进来的回调函数


12.贴出中间过程走过的处理函数

fix()函数中

(1)// 构造新的自定义的event对象(原有的对象为新对象的一个属性)
event = new jQuery.Event( originalEvent );

jQuery.Event = function( src, props ) {

// Allow instantiation without the 'new' keyword
if ( !(this instanceof jQuery.Event) ) {
return new jQuery.Event( src, props );
}


// Event object
if ( src && src.type ) {
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.returnValue === false ||
src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;


// Event type
} else {
this.type = src;
}

// Put explicitly provided properties onto the event object
// 如果有自定义提供的属性,也合并到this对象中
if ( props ) {
jQuery.extend( this, props );
}

// Create a timestamp if incoming event doesn't have one
this.timeStamp = src && src.timeStamp || jQuery.now();

// jQuery.expando 作为event是否被修复过的标识
// Mark it as fixed
this[ jQuery.expando ] = true;
};


// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
// 状态机制
isDefaultPrevented: returnFalse,
isPropagationStopped: returnFalse,
isImmediatePropagationStopped: returnFalse,


preventDefault: function() {
var e = this.originalEvent;


this.isDefaultPrevented = returnTrue;
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: 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: function() {
this.isImmediatePropagationStopped = returnTrue;
this.stopPropagation();
}
};

(2)return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; // event

根据事件类型调用对用事件类型的过滤函数,钩子机制,用来兼容一些属性名

13.handlerQueue = jQuery.event.handlers.call( this, event, handlers );


// 修复的event对象,回调函数对象
handlers: function( event, handlers ) {

var sel, handleObj, matches, i,
handlerQueue = [],
delegateCount = handlers.delegateCount, // 0表示普通事件
cur = event.target; //<div id="aaron">Aron test</div> a#a

// Find delegate handlers
// Black-hole SVG <use> instance trees (#13180)
// Avoid non-left-click bubbling in Firefox (#3861)
// 如果为委托事件,则执行下列代码
if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {


/* jshint eqeqeq: false */
// cur != this 循环到cur等于当前委托绑定的元素
for ( ; cur != this; cur = cur.parentNode || this ) {
/* jshint eqeqeq: true */

// Don't check non-elements (#13208)
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)

if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) {
matches = [];
for ( i = 0; i < delegateCount; i++ ) {

handleObj = handlers[ i ];


// Don't conflict with Object.prototype properties (#13203)
sel = handleObj.selector + " "; // a a a 

if ( matches[ sel ] === undefined ) {

matches[ sel ] = handleObj.needsContext ? // 1 0 0
jQuery( sel, this ).index( cur ) >= 0 :
jQuery.find( sel, this, null, [ cur ] ).length; // 调用Sizzle库,由complile动态生成查询函数,并且执行返回查询结果

}

// 如果查找到a,则把handleObj对象(包含回调函数及一些属性)push matches中
if ( matches[ sel ] ) {
matches.push( handleObj );
}
}
// 如果matches有回调对象压入,则将matches整个压入handlerQueue回调队列中

if ( matches.length ) {
handlerQueue.push({ elem: cur, handlers: matches });
}



}
}
}

// Add the remaining (directly-bound) handlers
// 0<1

if ( delegateCount < handlers.length ) {
handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
}

return handlerQueue;
},

该函数特别有针对委托事件类型的处理,其中

jQuery.find( sel, this, null, [ cur ] ).length; // 调用Sizzle库,由complile动态生成查询函数,并且执行返回查询结果

14.结束分析过程

四:总结

1.它将回调函数不是直接绑定在DOM上,而是放在数据仓库中,DOM元素中存放数据仓库钥匙jQuery[expand]

2.几乎将IE高低版本与其他浏览器兼容了,小到DOM元素的每个属性

3.每次绑定完成后,都会让传进来的elem= null ,大大降低了浏览器内存泄露的可能性

4.将IE中的event和其他浏览器的event统一起来运用。

5.简简单单的绑定一个点击事件,jQuery却做了如此多的工作,每一步都非常有意思

6.上面代码中很多 handler = events[type] = elemData.events代码,其实在JS中 针对于对象和数组,他们为引用关系,改变其中一个其他的都会改变

所以这也是为什么dispatch只传进了elem,event参数,因为可以通过elem获取缓存数据

7.只有看懂代码,进而才能分析其框架的架构和思路,写的好累。

8.最后上传一张借鉴的图.数据仓库模型


五:接下来会分析下委托代码,跟普通又什么区别


猜你喜欢

转载自blog.csdn.net/luolaifa000/article/details/44422911