1. 程式人生 > >jQuery原始碼-解除繫結事件函式unbind

jQuery原始碼-解除繫結事件函式unbind

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.最後借用一張圖,實在是覺得這個快取資料倉庫太經典了.(內部快取和外部快取)