jQuery源码-解除绑定事件函数unbind

来源:互联网 发布:php class 内调用方法 编辑:程序博客网 时间:2024/04/30 09:16

jQuery提供的解除事件绑定的接口,其实jQuery任何解除事件绑定的接口都最终会走this.off函数

解除委托委托绑定也是如此 undelegate接口函数也是最终走off函数

unbind: function( types, fn ) {

return this.off( types, null, fn );
}

下面就开始记录解除绑定的源码过程,HTML请自行补脑,这只是一个简单的例子,但中心思想不会变

$('#aaron').click(function(){ 
console.log('aaron');
});

$('#aaron').unbind();

一.直接转向off()函数:

off: function( types, selector, fn ) {
// click null callback
var handleObj, type;
if ( types && types.preventDefault && types.handleObj ) {
// ( event )  dispatched jQuery.Event
handleObj = types.handleObj;
jQuery( types.delegateTarget ).off(
handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
handleObj.selector,
handleObj.handler
);
return this;
}
// 如果这里types是对象,则遍历解除绑定
if ( typeof types === "object" ) {
// ( types-object [, selector] )
for ( type in types ) {
this.off( type, selector, types[ type ] );
}
return this;
}

if ( selector === false || typeof selector === "function" ) {
// ( types [, fn] )
fn = selector;
selector = undefined;
}
if ( fn === false ) {
fn = returnFalse;
}

return this.each(function() {
// div#aaron click callback null
// 这里就交给remove处理
jQuery.event.remove( this, types, fn, selector );
});
},

二.可以看到off函数的return处,还要执行jQuery.event.remove函数,作用总的来说是清除缓存数据,和解除DOM绑定的事件

// Detach an event or set of events from an element
// 将elemData该事件的缓存数据清除,并最终调用浏览器接口解除DOM绑定-addEventListener
remove: function( elem, types, handler, selector, mappedTypes ) {
// div#aaron "click" undefined null undefined
var j, handleObj, tmp,
origCount, t, events,
special, handlers, type,
namespaces, origType,
// 获取缓存,如果之前绑定过事件,则elemData不为空
elemData = jQuery.hasData( elem ) && jQuery._data( elem );

if ( !elemData || !(events = elemData.events) ) {
return;
}

// Once for each type.namespace in types; type may be omitted
// /\S+/g  匹配非空白字符
types = ( types || "" ).match( core_rnotwhite ) || [""];// ["click"]

t = types.length;//1
while ( t-- ) {
// /^([^.]*)(?:\.(.+)|)$/  example click.name 名空间
tmp = rtypenamespace.exec( types[t] ) || [];

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

// Unbind all events (on this namespace, if provided) for the element
// 如果事件类型为空,则清除所有事件的缓存数据
if ( !type ) {
for ( type in events ) {
// 将elemData该事件的缓存数据清除,并最终调用浏览器接口解除DOM绑定-addEventListener
jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
}
continue;
}

special = jQuery.event.special[ type ] || {};

type = ( selector ? special.delegateType : special.bindType ) || type;//click

handlers = events[ type ] || [];//取出type事件类型的缓存数据

tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );//undefined

// Remove matching events
origCount = j = handlers.length;// 1


while ( j-- ) {
handleObj = handlers[ j ];//单个的回调函数对象

if ( ( mappedTypes || origType === handleObj.origType ) &&
( !handler || handler.guid === handleObj.guid ) &&
( !tmp || tmp.test( handleObj.namespace ) ) &&
( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
// 从j位置开始删除一个对象 
handlers.splice( j, 1 );//清除elemData.events属性 type类型的缓存数据
// 删除后handlers 中只有delegateCount属性
if ( handleObj.selector ) {
handlers.delegateCount--;
}

if ( special.remove ) {
special.remove.call( elem, handleObj );
}
}
}

// Remove generic event handler if we removed something and no more handlers exist
// (avoids potential for endless recursion during removal of special event handlers)

if ( origCount && !handlers.length ) {

if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {


jQuery.removeEvent( elem, type, elemData.handle );//移除事件 removeEventListener/detachEvent

}


delete events[ type ];//销毁element.events->click属性信息
}
}

// Remove the expando if it's no longer used
// 如果element.events的type事件数据清空,并且没有其他事件类型的数据,则销毁events和handle属性

if ( jQuery.isEmptyObject( events ) ) {
delete elemData.handle;//销毁 elemData->handle属性

// removeData also checks for emptiness and clears the expando if empty
// so use it instead of delete
// 到这里elem这里只剩下events属性
jQuery._removeData( elem, "events" );//销毁 elemData->events属性和对应的缓存数据
}
},

**注意有这样一段代码:tmp = rtypenamespace.exec( types[t] ) || [];这是为划分开名空间下的click事件.一般来说不会用到

(1)最开始的时候就获取事件缓存数据(它属于内部缓存,所以是放在cache[id]中)

(2)如果types是以空格隔开的多类型字符串:click mouseover ...等,就要遍历删除,或者为空的话,删除全部的事件缓存,我们这只是click

(3)handlers = events[ type ] || []; //取出type事件类型的缓存数据 handlers.splice( j, 1 );// 清除elemData.events属性 type类型的缓存数据,从后往前一个一个删除

(4)jQuery.removeEvent( elem, type, elemData.handle );//移除事件 removeEventListener/detachEvent   解除DOM事件的绑定

(5)delete events[ type ]; //销毁element.events->click属性信息

(6)delete elemData.handle; //销毁 elemData->handle属性

(7)jQuery._removeData( elem, "events" ); // 销毁 elemData->events属性和对应的缓存数据      最后这里又进入这个函数


三._removeData: function( elem, name ) { //只是作为跳转到internalRemoveData的中转接口

return internalRemoveData( elem, name, true );//
},


四.internalRemoveData( elem, name, true ); // 销毁 elemData->events属性和对应的缓存数据

function internalRemoveData( elem, name, pvt ) {
// div#arron "event" true
if ( !jQuery.acceptData( elem ) ) {
return;
}


var thisCache, i,
isNode = elem.nodeType,


// See jQuery.data for more information
cache = isNode ? jQuery.cache : elem,
id = isNode ? elem[ jQuery.expando ] : jQuery.expando;//1

// If there is already no cache entry for this object, there is no
// purpose in continuing

if ( !cache[ id ] ) {
return;
}


if ( name ) {


thisCache = pvt ? cache[ id ] : cache[ id ].data;//以id为键值取出来的缓存数据

if ( thisCache ) {

// Support array or space separated string names for data keys
if ( !jQuery.isArray( name ) ) {

// try the string as a key before any manipulation
if ( name in thisCache ) {

name = [ name ];  // ["events"]
} else {


// split the camel cased version by spaces unless a key with the spaces exists
name = jQuery.camelCase( name );

if ( name in thisCache ) {
name = [ name ];
} else {
name = name.split(" ");
}
}
} else {
// If "name" is an array of keys...
// When data is initially created, via ("key", "val") signature,
// keys will be converted to camelCase.
// Since there is no way to tell _how_ a key was added, remove
// both plain key and camelCase key. #12786
// This will only penalize the array argument path.
name = name.concat( jQuery.map( name, jQuery.camelCase ) );
}


i = name.length; // 1 

while ( i-- ) {

delete thisCache[ name[i] ];//delete thisCache["events"]

}
// thisCache = {}

// If there is no data left in the cache, we want to continue
// and let the cache object itself get destroyed

if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {
return;
}
}
}


// See jQuery.data for more information

if ( !pvt ) {
delete cache[ id ].data;


// Don't destroy the parent cache unless the internal data object
// had been the only thing left in it
if ( !isEmptyDataObject( cache[ id ] ) ) {
return;
}
}

// Destroy the cache = delete cache[id],delete elem[expand]
if ( isNode ) {
jQuery.cleanData( [ elem ], true );


// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
/* jshint eqeqeq: false */
} else if ( jQuery.support.deleteExpando || cache != cache.window ) {
/* jshint eqeqeq: true */
delete cache[ id ];


// When all else fails, null
} else {
cache[ id ] = null;
}
}


(1)cache = isNode ? jQuery.cache : elem,id = isNode ? elem[ jQuery.expando ] : jQuery.expando;//1

获取缓存数据仓库cache,在获取该元素在缓存数据仓库的钥匙id

(2)thisCache = pvt ? cache[ id ] : cache[ id ].data;//以id为键值取出来的缓存数据       pvt为ture  事件类型缓存数据为内部缓存

(3)while ( i-- ) { //销毁缓存中events属性

delete thisCache[ name[i] ];//delete thisCache["events"]

}

(4)jQuery.cleanData( [ elem ], true );  又是一个函数// Destroy the cache = delete cache[id],delete elem[expand]


五.cleanData: function( elems, /* internal */ acceptData ) {
// [span.click, item: function, namedItem: function]
var elem, type, id, data,
i = 0,
internalKey = jQuery.expando,//仓库钥匙
cache = jQuery.cache,//缓存仓库
deleteExpando = jQuery.support.deleteExpando,//true
special = jQuery.event.special;//一些特殊事件的handle

// 遍历清空缓存
for ( ; (elem = elems[i]) != null; i++ ) {

if ( acceptData || jQuery.acceptData( elem ) ) {

id = elem[ internalKey ];

data = id && cache[ id ];

if ( data ) {

if ( data.events ) {
for ( type in data.events ) {
if ( special[ type ] ) {
jQuery.event.remove( elem, type );//清除事件


// This is a shortcut to avoid jQuery.event.remove's overhead
} else {

jQuery.removeEvent( elem, type, data.handle );
}
}
}

// Remove cache only if it was not already removed by jQuery.event.remove
if ( cache[ id ] ) {


delete cache[ id ];//销毁cache[id]

// IE does not allow us to delete expando properties from nodes,
// nor does it have a removeAttribute function on Document nodes;
// we must handle all of these cases
if ( deleteExpando ) {

delete elem[ internalKey ];


} else if ( typeof elem.removeAttribute !== core_strundefined ) {
elem.removeAttribute( internalKey );


} else {
elem[ internalKey ] = null;
}
// 将当前页面删除的缓存钥匙收集起来,以备循环利用
core_deletedIds.push( id );

}
}
}
}
},

(1)该函数的执行前提是,cache[id]中的数据都为空了

(2)delete cache[ id ]; //销毁cache[id]     elem[ internalKey ] = null;   // 销毁钥匙

(3)core_deletedIds.push( id ); //将当前页面删除的缓存钥匙收集起来,以备循环利用

六:到此jQuery解除绑定代码走完。总结如下

1.一个小小解除事件绑定功能,为什么也要走这么对代码,估计主要是为了数据缓存仓库和绑定优化吧 !

2. 如果还不明白的话,可在chrome中一步一步console.log出数据看!

3.最后借用一张图,实在是觉得这个缓存数据仓库太经典了.(内部缓存和外部缓存)



0 0
原创粉丝点击