jQuery源码分析之jQuery.cleanData和jQuery.removeData以及jQuery._removeData十五问

来源:互联网 发布:鬼气凛然实体书淘宝 编辑:程序博客网 时间:2024/09/21 08:16

具体分析可以参见这两博客:点开   点击打开链接

提前阅读我上一篇博客:打开

测试代码1:

 alert($.support.deleteExpando);//打印true,表示是否支持删除附加在DOM Element上面的数据 cache != cache.window表示cache不是window对象,如果是window对象抛出异常// 见下面的例子:(在IE中无法用delete删除直接报错!) window.sex="xxx";try{ alert("删除之前->"+window.sex); alert(delete window.sex);// alert("删除之后->"+window.sex);}catch(e){ alert("删除出错了!");}
测试代码2:

 var obj=new Object(); alert(obj.nodeType);//直接返回undefined
测试代码3:

 var obj=new Object();var nodeType=obj.nodeType;  var result=nodeType?1:2; alert(result);//因为JS对象没有nodeType会返回undefined
代码4:弄清楚什么是钥匙

//如果是Node,获取钥匙elem[internalKey]$("#div").data("name", "qinliang");$("h1").data("name", "qinliang");$("h1").data("sex", "male");$("#p").data("sex", "male"); alert(jQuery.expando); alert($("h1").nodeType);//jQuery对象的没有nodeType属性,但是DOM对象有 alert($("#div")[0][jQuery.expando]);//返回1 alert($("h1")[0][jQuery.expando]);//返回2 alert($("#p")[0][jQuery.expando]);//返回3,因为第二个保存数据
代码5:通过钥匙获取保存数据:

$("#div").data("name", "qinliang");$("h1").data("name", "qinliang");$("h1").data("sex", "male");$("#p").data("sex", "male");alert($("#div")[0][jQuery.expando]);alert(jQuery.cache[id].name);//这时候打印undefined,因为这里是用户数据,他是保存在data域下面的,此处id=$("#div")[0][jQuery.expando],通过jQuery.cache[id].data.name

下面是jQuery.camelCase方法源码:

     rmsPrefix = /^-ms-/,rdashAlpha = /-([\da-z])/gi,// Used by jQuery.camelCase as callback to replace()fcamelCase = function( all, letter ) {return letter.toUpperCase();};
jQuery.camelCase代码如下:

camelCase: function( string ) {/*对伊rmsPrefix的正则表达式正则rmsPrefix用于匹配字符串中前缀“-ms-”,匹配部分会被替换为“ms-”。这么做是因为在IE中,连字符式的样式名前缀“-ms-”对应小写的“ms”,而不是驼峰式的“Ms”。例如,“-ms-transform”对应“msTransform”而不是“MsTransform”。在IE以外的浏览器中,连字符式的样式名则可以正确地转换为驼峰式,例如,“-moz-transform”对应“MozTransform”。1,也就是第一个replace把-ms-transform修改成为"ms-transform"2,第二个正则表达式rdashAlpha就是搜索-t,也就是横和后面的第一个字符,在函数fcamelCase里面第二个参数就是                   第一个捕获组里面的东西然后把第一个捕获组里面的首字母大写就可以了!但是,如果是数字,toUppercase以后没有变化!*/return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );}
问题1:jQuery.cleanData是如何删除数据的?删除了那些数据?

这个方法删除了内部所有的events,也就是jQuery内部绑定的事件全部删除了!

                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 );}}}
问题2:为什么有jQuery.event.remove和jQuery.removeEvent两种删除事件的方式,那些事件是放在jQuery.event.special中的?

我们来看看jQuery.event.special中有那些事件

special: {load: {// Prevent triggered image.load events from bubbling to window.loadnoBubble: true},focus: {// Fire native event if possible so blur/focus sequence is correcttrigger: function() {if ( this !== safeActiveElement() && this.focus ) {try {this.focus();return false;} catch ( e ) {// Support: IE<9// If we error on focus to hidden element (#1486, #12518),// let .trigger() run the handlers}}},delegateType: "focusin"},blur: {trigger: function() {if ( this === safeActiveElement() && this.blur ) {this.blur();return false;}},delegateType: "focusout"},click: {// For checkbox, fire native event so checked state will be righttrigger: function() {//special.trigger.apply( elem, data )if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {this.click();return false;}},// For cross-browser consistency, don't fire native .click() on links_default: function( event ) {return jQuery.nodeName( event.target, "a" );}},beforeunload: {postDispatch: function( event ) {// Support: Firefox 20+// Firefox doesn't alert if the returnValue field is not set.if ( event.result !== undefined && event.originalEvent ) {event.originalEvent.returnValue = event.result;}}}}
note:我们看到load事件,focus事件,blur事件,click事件和beforeunload事件全部放在了jQuery.event.special中;load是为了防止他冒泡,如img冒泡到window上,focus和blur是因为他们不冒泡,click是为了触发元素本身的click事件等!
mouseOver,mouseOut,pointerOver,pointerOut也是特殊事件

jQuery.each({mouseenter: "mouseover",mouseleave: "mouseout",pointerenter: "pointerover",pointerleave: "pointerout"}, function( orig, fix ) {jQuery.event.special[ orig ] = {delegateType: fix,bindType: fix,handle: function( event ) {var ret,target = this,related = event.relatedTarget,handleObj = event.handleObj;// For mousenter/leave call the handler if related is outside the target.// NB: No relatedTarget if the mouse left/entered the browser windowif ( !related || (related !== target && !jQuery.contains( target, related )) ) {event.type = handleObj.origType;ret = handleObj.handler.apply( this, arguments );event.type = fix;}return ret;}};});
Submit也是特殊事件

// IE submit delegationif ( !support.submitBubbles ) {jQuery.event.special.submit = {setup: function() {// Only need this for delegated form submit eventsif ( jQuery.nodeName( this, "form" ) ) {return false;}// Lazy-add a submit handler when a descendant form may potentially be submittedjQuery.event.add( this, "click._submit keypress._submit", function( e ) {// Node name check avoids a VML-related crash in IE (#9807)var elem = e.target,form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;if ( form && !jQuery._data( form, "submitBubbles" ) ) {jQuery.event.add( form, "submit._submit", function( event ) {event._submit_bubble = true;});jQuery._data( form, "submitBubbles", true );}});// return undefined since we don't need an event listener},postDispatch: function( event ) {// If form was submitted by the user, bubble the event up the treeif ( event._submit_bubble ) {delete event._submit_bubble;if ( this.parentNode && !event.isTrigger ) {jQuery.event.simulate( "submit", this.parentNode, event, true );}}},teardown: function() {// Only need this for delegated form submit eventsif ( jQuery.nodeName( this, "form" ) ) {return false;}// Remove delegated handlers; cleanData eventually reaps submit handlers attached abovejQuery.event.remove( this, "._submit" );}};}
changge事件也是特殊事件

if ( !support.changeBubbles ) {jQuery.event.special.change = {setup: function() {if ( rformElems.test( this.nodeName ) ) {// IE doesn't fire change on a check/radio until blur; trigger it on click// after a propertychange. Eat the blur-change in special.change.handle.// This still fires onchange a second time for check/radio after blur.if ( this.type === "checkbox" || this.type === "radio" ) {jQuery.event.add( this, "propertychange._change", function( event ) {if ( event.originalEvent.propertyName === "checked" ) {this._just_changed = true;}});jQuery.event.add( this, "click._change", function( event ) {if ( this._just_changed && !event.isTrigger ) {this._just_changed = false;}// Allow triggered, simulated change events (#11500)jQuery.event.simulate( "change", this, event, true );});}return false;}// Delegated event; lazy-add a change handler on descendant inputsjQuery.event.add( this, "beforeactivate._change", function( e ) {var elem = e.target;if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {jQuery.event.add( elem, "change._change", function( event ) {if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {jQuery.event.simulate( "change", this.parentNode, event, true );}});jQuery._data( elem, "changeBubbles", true );}});},handle: function( event ) {var elem = event.target;// Swallow native change events from checkbox/radio, we already triggered them aboveif ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {return event.handleObj.handler.apply( this, arguments );}},//上下问为DOM,第一个参数是namespace,第二个参数是通用回调函数teardown: function() {jQuery.event.remove( this, "._change" );return !rformElems.test( this.nodeName );}};}
focusin和focusout也是特殊事件

// Create "bubbling" focus and blur eventsif ( !support.focusinBubbles ) {jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {// Attach a single capturing handler on the document while someone wants focusin/focusoutvar handler = function( event ) {jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );};jQuery.event.special[ fix ] = {setup: function() {var doc = this.ownerDocument || this,attaches = jQuery._data( doc, fix );if ( !attaches ) {doc.addEventListener( orig, handler, true );}jQuery._data( doc, fix, ( attaches || 0 ) + 1 );},teardown: function() {var doc = this.ownerDocument || this,attaches = jQuery._data( doc, fix ) - 1;if ( !attaches ) {doc.removeEventListener( orig, handler, true );jQuery._removeData( doc, fix );} else {jQuery._data( doc, fix, attaches );}}};});}
问题3:jQuery.event.remove是如何移除事件的?

在实例方法off中是这样调用的

off: function( types, selector, fn )//off方法函数签名,移除符合selector选择器的元素的事件jQuery.event.remove( this, types, fn, selector );//

jQuery.cleanData中是这么调用的

jQuery.event.remove( elem, type );//只是传入了元素和类型
我们看看移除事件的逻辑步骤是怎么样的:

第一步:获取该元素的events域,因为事件都是保存在events域下面的:

elemData = jQuery.hasData( elem ) && jQuery._data( elem );if ( !elemData || !(events = elemData.events) ) {//获取events域return;}
第二步:因为可以同时移除多个事件类型,所以传入的移除事件参数可以是一个数组,然后遍历数组,获取移除的type的类型名称(click等)和命名空间(用于和events域下面的特定的handleObj进行对象)

types = ( types || "" ).match( rnotwhite ) || [ "" ];t = types.length;while ( t-- ) {//rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;  //如果是click.test,最后就是数组[click.test,click,test]tmp = rtypenamespace.exec( types[t] ) || [];//原始类型type = origType = tmp[1];//获取事件空间,同时排序是为了后面对比空间是否相同的,如text.test和test.text是相同的,因为都sort所以可以移除!namespaces = ( tmp[2] || "" ).split( "." ).sort();// Unbind all events (on this namespace, if provided) for the elementif ( !type ) {//如果传入的types是空字符串,那么移除所有的事件!for ( type in events ) {jQuery.event.remove( elem, type + types[ t ], handler, selector, true );}continue;}//从jQuery.event.special中获取事件类型,处理特殊的事件类型special = jQuery.event.special[ type ] || {};//如果传入了selector,表示是移除代理事件!type = ( selector ? special.delegateType : special.bindType ) || type;//获取jQuery内部数据events中的同类的事件!handlers = events[ type ] || [];//把已经排序好了的命名空间进行重新组合!tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );//handlers就是需要移除的所有的回调函数,其是handleObj对象!handlers = events[ type ] || [];// Remove matching eventsorigCount = j = handlers.length;
第三步:把我们传入的每一个事件的类型(如click),命名空间(namespace),guid值,selector值和特定的events域下面的handleObj的这些参数进行对比,如果相同则移除,并且把delegateType自减

                       while ( j-- ) {//移除的事件必须是类型相同,guid相同,namespace相同,同时传入的selector和他的selector相同!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 ) ) {handlers.splice( j, 1 );if ( handleObj.selector ) {handlers.delegateCount--;//代理次数自减}//如果special有remove,调用他的remove!if ( special.remove ) {special.remove.call( elem, handleObj );}}}
从上面看到,如果是特殊事件同时含有remove方法,那么我们需要调用他自己的remove方法来完成
第四步:如果该事件的回调函数的移除以后已经是空的,也就是没有更多的同类回调函数,那么删除events域下面的同类事件的空间

//如果移除事件以后,回调的events已经是空了,那么调用jQuery.Event移除!if ( origCount && !handlers.length ) {if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {jQuery.removeEvent( elem, type, elemData.handle );}delete events[ type ];}
第五步:更有甚者,如果移除了所有指定的事件以后,events域本身,而不仅仅是events域下面的特殊事件为空了,那么连同events域一起移除

 //如果events已经是空了,那么删除handle域// Remove the expando if it's no longer usedif ( jQuery.isEmptyObject( events ) ) {delete elemData.handle;             //同时删除events域!// removeData also checks for emptiness and clears the expando if empty// so use it instead of deletejQuery._removeData( elem, "events" );}
问题4:delegateCount是什么鬼,有什么用?

通过该图我们看到click事件对应的回调数组里面有一个delegateCount参数表示该DOM代理的click事件的个数。他表示,如果我们在调用click事件的时候传入了selector,那么表示满足selector的DOM元素的click事件都是被这个DOM代理的,于是该DOM的delegateCount就会自增!看下例子:

 HTML部分

<div id="div" style="width:100px;height:100px;background-color:red;">   <p style="width:100px;height:50px;background-color:#ccc;">I am p</p></div>
JS部分

$("div").on("click", "p", function(event){});var expando=jQuery.expando;//准备获取钥匙var key=$("div")[0][expando];//获取钥匙var walhouse=jQuery.cache;//获取仓库var data=walhouse[key];//通过钥匙打开仓库console.log(data);
note:通过该图你就可以看出,这种调用方式传入了selector,所以在div元素的click事件上面就会出现delegateCount字段。同时,通过该图你也应该知道每一个事件类型都可能有一个单独的delegateCount字段,表示该元素代理的其它的元素的同类的事件的个数。
问题5:我连什么是handleObj都不知道,handleObj中保存的数据格式神马?

通过该图就会知道handleObj具有特殊的格式,包括origType,namespace,guid等属性
问题6:如何把一个DOM对象上面所有的事件都删除?

解答:通过给jQuery.event.remove传入一个空字符串就能够完成!

           $("div").on("click", "p", function(event){});$("div").on("mousover", "p", function(event){});jQuery.event.remove($("div")[0],"");//移除所有的事件,不限制事件的类型!var expando=jQuery.expando;//准备获取钥匙var key=$("div")[0][expando];//获取钥匙var walhouse=jQuery.cache;//获取仓库var data=walhouse[key];//通过钥匙打开仓库console.log(data);
当然jQuery没有给我们暴露jQuery.event.remove方法,但是我们可以通过off方法间接完成

              $("div").on("click", "p", function(event){});$("div").on("mousover", "p", function(event){});$("div").off("");//通过jQuery暴露的函数off移除元素上面所有的事件!var expando=jQuery.expando;//准备获取钥匙var key=$("div")[0][expando];//获取钥匙var walhouse=jQuery.cache;//获取仓库var data=walhouse[key];//通过钥匙打开仓库console.log(data);
问题7:为什么上面这种方式可以移除所有的事件?

                         if ( !type ) {//如果传入的types是空字符串,那么移除所有的事件!for ( type in events ) {//events域对应的是所有的click,mouseover等事件的回调函数数组,每一个事件一个回调数组  jQuery.event.remove( elem, type + types[ t ], handler, selector, true );//for循环移除type为click等,types[t]为""}continue;}
 note:上面这种方式通过传入空字符串方式,那么handler,selector都是undefined,第四个参数为true,同时源码中的tmp也是false,因为这时候没有命名空间,因此必定删除元素的所有的事件

                          if ( ( mappedTypes || origType === handleObj.origType ) &&( !handler || handler.guid === handleObj.guid ) &&( !tmp || tmp.test( handleObj.namespace ) ) &&( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {handlers.splice( j, 1 );//必定删除所有事件,因为if必为true!if ( handleObj.selector ) {//代理事件自减handlers.delegateCount--;}
note:因为本身的回调函数已经删除了,所以即使我以前代理了事件,因为我的回调已经删除了,那么我也不会代理,所以delegateCount自减!
至于后面这一句代码,暂时没有找到源码中有那么speical事件有remove方法,可能是为了未来做准备的把

if ( special.remove ) {special.remove.call( elem, handleObj );}
问题8:jQuery.event.remove已经清楚了,那么jQuery.removeEvent是神马?

解答:哈哈哈,回到我们熟悉的地盘了

jQuery.removeEvent = document.removeEventListener ?function( elem, type, handle ) {if ( elem.removeEventListener ) {elem.removeEventListener( type, handle, false );//冒泡阶段处理}} ://如果是IE浏览器那么也要移除事件,调用detachEvent方法function( elem, type, handle ) {var name = "on" + type;if ( elem.detachEvent ) {// #8545, #7054, preventing memory leaks for custom events in IE6-8// detachEvent needed property on element, by name of that event, to properly expose it to GCif ( typeof elem[ name ] === strundefined ) {elem[ name ] = null;}elem.detachEvent( name, handle );}};
所以下面这段代码也就好理解了

           if ( origCount && !handlers.length ) {if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {jQuery.removeEvent( elem, type, elemData.handle );//所有函数调用的句柄}delete events[ type ];}
note:也就是说,对于没有teardown方法或者调用该方法返回的值为false的时候,那么我们就用我们熟悉的方法来移除事件,其中elemData.handler就是一个通用的函数,所有事件的调用都是通过该通用函数来触发的,我们也可以看看他的源码

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 unloadedreturn typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ?jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :undefined;};// Add elem as a property of the handle fn to prevent a memory leak with IE non-native eventseventHandle.elem = elem;}
这是在jQuery.event.add中添加的通用的回调函数,所有绑定的事件都是通过这个handle来调用的!同时,通过该图我们知道,之所以称之为通用的,是因为该回调函数是和events并列的,而不在具体的事件下面,即如不是和click等并列的!那么这种方式有什么好处?因为上面我们熟悉的那种移除事件的方式必须回调函数完全一样,才能移除事件,所以才引入这个通用的函数用于移除事件的!
那么下面这段代码也就好理解了,我们直接删除了这个handle了,因为所有的事件都不存在了,那么这个回调函数也没有存在的必要了

if ( jQuery.isEmptyObject( events ) ) {delete elemData.handle;//回调函数没有存在的必要了,因为事件都没有了                         //同时删除events域!// removeData also checks for emptiness and clears the expando if empty// so use it instead of deletejQuery._removeData( elem, "events" );//最后删除events域}
问题9:那么对于上面那些特殊的事件,那么我们应该如何移除?

special.teardown.call( elem, namespaces, elemData.handle ) === false )//自定义事件单独移除

因为这一类事件是自定义的事件,所以添加他们的时候是通过自定义事件来完成的,所以移除他们也要有特殊的方法。我们先弄清楚,我们传入的上下文为DOM,第一个参数是namespace,第二个参数是通用的回调函数。我们以后专门针对这种情况进行讨论

问题10:上面讲了这么多,怎么都不来讨论jQuery.cleanData?

internalRemoveData中调用

jQuery.cleanData( [ elem ], true );
实例remove方法调用
jQuery.cleanData( getAll( elem ) );
作用:清除传入的一个DOM集合中的所有的数据,记住只能是DOM元素数组
我们知道,他会移除元素上面绑定的events对象,但是元素上面还绑定了数据啊,他是怎么处理的?

                   //经过上面的逻辑,这里所有的如click,mouseover等对应的handleObj数组全部是空的!因为上面的remove就是删除handleObj!// Remove cache only if it was not already removed by jQuery.event.remove//已经没有回调要仓库干嘛?在jQuery.event.handle中已经移除了handle回调函数了!if ( cache[ id ] ) {delete 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 casesif ( deleteExpando ) {//销毁钥匙delete elem[ internalKey ];} else if ( typeof elem.removeAttribute !== strundefined ) {elem.removeAttribute( internalKey );} else {elem[ internalKey ] = null;}deletedIds.push( id );}
note:这里经过了两步,第一步把仓库删除,第二步连钥匙也销毁了,这一点要特别注意,于是以后要获取到该元素上面的数据仓库或者事件仓库都是无稽之谈!
看看这里就知道了,但是注意传入cleanData参数是数组

       $("#div").on("click", "p", function(event){});$("#div").on("mousover", "p", function(event){});   $("#div").data("name","qinliang");var expando=jQuery.expando;//准备获取钥匙var key=$("div")[0][expando];//获取钥匙var walhouse=jQuery.cache;//获取仓库var data=walhouse[key];//通过钥匙打开仓库console.log(data);         jQuery.cleanData([$("#div")[0]]);//销毁仓库,同时把仓库的钥匙也扔了!看清楚这里是数组才可!   var data1=$("#div").data("name");   console.log(data1);//上面的仓库也销毁了,钥匙也扔了,当然拿不到了!
note:调用cleanData以后,仓库被烧,钥匙被销毁,所有的证据被销毁(也就是所有的事件被移除了),cleanData有点日本人入侵的感觉!
问题11:jQuery源码中那些方法会有日本人入侵的感觉?

实例remove方法,在删除自己之前会移除自己所有的子元素+自身的所有的数据和事件

          if ( !keepData && elem.nodeType === 1 ) { //remove时候移除自身+所有子元素的数据和事件防止内存泄漏jQuery.cleanData( getAll( elem ) );}
实例html方法,替代内部的html时候把内部的html的数据和事件移除

                                      try {for (; i < l; i++ ) {// Remove element nodes and prevent memory leakselem = this[i] || {};if ( elem.nodeType === 1 ) {jQuery.cleanData( getAll( elem, false ) );//移除子元素,只是子元素elem.innerHTML = value;}}
实例replaceWith方法,把一个元素替换掉的时候要清除子元素+自身的内部数据和事件

jQuery.cleanData( getAll( this ) );//子元素和自身的所有数据和事件
问题12:cleanData第二个参数有什么用吗?

cleanData: function( elems, /* internal */ acceptData ) {var elem, type, id, data,i = 0,internalKey = jQuery.expando,cache = jQuery.cache,deleteExpando = support.deleteExpando,special = jQuery.event.special;for ( ; (elem = elems[i]) != null; i++ ) {if ( acceptData || jQuery.acceptData( elem ) ) {//只在internalData中调用传入true,传入true那么不会判断jQuery.acceptData! } }
问题13:我们看看internalRemoveData?

    $("#div").on("click", "p", function(event){alert("click");});$("#div").on("mousover", "p", function(event){});   $("#div").data("name","qinliang");var expando=jQuery.expando;//准备获取钥匙var key=$("div")[0][expando];//获取钥匙var walhouse=jQuery.cache;//获取仓库var data=walhouse[key];//通过钥匙打开仓库console.log(data);       jQuery.removeData($("#div")[0],"name");//底层会销毁data域,以后通过data方法无法获取用户自定义数据!    var expando1=jQuery.expando;//准备获取钥匙var key1=$("div")[0][expando1];//获取钥匙var walhouse1=jQuery.cache;//获取仓库var data1=walhouse[key1];//通过钥匙打开仓库       console.log(data1);//发现把data域都删除了
通过该图你会发现,我们调用了jQuery.removeData以后把整个data域都删除了,虽然我明确指定了删除"name",这时候通过data方法获取用户自定义数据都是不存在的!
调用该方法删除了data域以后,如果仓库不是空的,那么返回:

if ( !pvt ) {delete cache[ id ].data;console.log(cache[ id ]);// Don't destroy the parent cache unless the internal data object// had been the only thing left in itif ( !isEmptyDataObject( cache[ id ] ) ) {//如果非空那么返回!return;}}
下面这种情况,删除了数据域以后,那么仓库就是空的(因为没有绑定任何事件),那么我们"烧了仓库,丢了钥匙,毁灭所有证据"
   $("#div").data("name","qinliang");     $("#div").data("sex","female");var expando=jQuery.expando;//准备获取钥匙var key=$("div")[0][expando];//获取钥匙var walhouse=jQuery.cache;//获取仓库var data=walhouse[key];//通过钥匙打开仓库//console.log(data);         jQuery.removeData($("#div")[0],["name","sex"]);//销毁仓库,同时把仓库的钥匙也扔了!看清楚这里是数组才可!          var expando1=jQuery.expando;//准备获取钥匙var key1=$("#div")[0][expando1];//获取钥匙var walhouse1=jQuery.cache;//获取仓库var data1=walhouse[key1];//通过钥匙打开仓库     console.log(data1);//发现undefined,因为整个仓库都被销毁了!
这时候删除了数据域是空的,那么我们会销毁整个仓库

//如果是DOM,那么调用cleanDataif ( 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 ( support.deleteExpando || cache != cache.window ) {/* jshint eqeqeq: true */delete cache[ id ];// When all else fails, null} else {cache[ id ] = null;}
我们总结一下internalRemoveData的逻辑;

第一步:把我们传入的要移除的键名变成一个数组,然后逐个移除

if ( !jQuery.acceptData( elem ) ) {return;}var thisCache, i,isNode = elem.nodeType,// See jQuery.data for more informationcache = isNode ? jQuery.cache : elem,//获取仓库id = isNode ? elem[ jQuery.expando ] : jQuery.expando;//获取钥匙// If there is already no cache entry for this object, there is no// purpose in continuingif ( !cache[ id ] ) {return;}if ( name ) {//如果pvt是true那么我们获取内部数据,如果用户数据那么获取用户自定义数据,也就是data域下面!thisCache = pvt ? cache[ id ] : cache[ id ].data;if ( thisCache ) {// Support array or space separated string names for data keysif ( !jQuery.isArray( name ) ) {// try the string as a key before any manipulationif ( name in thisCache ) {name = [ name ];} else {// split the camel cased version by spaces unless a key with the spaces existsname = 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 ) );}
第二步:通过上面获取到要移除的name数组,逐个从数据仓库中移除,如果是用户数据从data域中移除,否则从仓库中直接移除

                      i = name.length;while ( i-- ) {delete thisCache[ name[i] ];}// If there is no data left in the cache, we want to continue// and let the cache object itself get destroyedif ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {return;}}
第三步:如果是用户数据,我们销毁整个data域,同时如果data域不存在的时候,还有事件等绑定存在,那么我们返回

if ( !pvt ) {delete cache[ id ].data;// Don't destroy the parent cache unless the internal data object// had been the only thing left in itif ( !isEmptyDataObject( cache[ id ] ) ) {//如果data域删除以后还有事件绑定我们返回return;}}
第四步:如果删除了data域以后仓库已经空了,那么我们我们把整个仓库都销毁

//如果是DOM,那么调用cleanDataif ( 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 ( support.deleteExpando || cache != cache.window ) {/* jshint eqeqeq: true */delete cache[ id ];// When all else fails, null} else {cache[ id ] = null;//销毁仓库}
注意:调用jQuery.removeData可以传入一个要移除的数据组成的数组

       jQuery.removeData($("#div")[0],["name","sex"]);//传入数组,在internalRemoveData中可以处理的,通过concat把驼峰和原数组合并
问题14:那些方法调用了internalRemoveData用来销毁data域,并且销毁了data域以后如果没有绑定事件还要销毁仓库?
jQuery.removeData来移除用户数据

removeData: function( elem, name ) {return internalRemoveData( elem, name );}
jQuery._removeData来移除内部数据

_removeData: function( elem, name ) {return internalRemoveData( elem, name, true );//true用来判断数据是在data域下面,还是在仓库里面。}
只有在用户数据的时候才要销毁data域,如果销毁了data域不存在事件才会销毁仓库;至于内部数据,反正会销毁仓库!

问题15:jQuery.cleanData,internalRemoveData源码

cleanData: function( elems, /* internal */ acceptData ) {var elem, type, id, data,i = 0,internalKey = jQuery.expando,cache = jQuery.cache,        deleteExpando = support.deleteExpando,special = jQuery.event.special;//因为删除的是DOM数组,所以首先遍历出每一个DOMfor ( ; (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.removejQuery.event.remove( elem, type );// This is a shortcut to avoid jQuery.event.remove's overhead} else {//是否豁然开朗,handle函数即通用回调函数在仓库上!也就是他不是和click等在一个域下面的!jQuery.removeEvent( elem, type, data.handle );}}}if ( cache[ id ] ) {delete 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 casesif ( deleteExpando ) {delete elem[ internalKey ];} else if ( typeof elem.removeAttribute !== strundefined ) {elem.removeAttribute( internalKey );} else {elem[ internalKey ] = null;}deletedIds.push( id );}}}}}
internalRemoveData源码.
function internalRemoveData( elem, name, pvt ) {if ( !jQuery.acceptData( elem ) ) {return;}var thisCache, i,isNode = elem.nodeType,// See jQuery.data for more informationcache = isNode ? jQuery.cache : elem,//获取仓库id = isNode ? elem[ jQuery.expando ] : jQuery.expando;//获取钥匙// If there is already no cache entry for this object, there is no// purpose in continuingif ( !cache[ id ] ) {return;}if ( name ) {//如果pvt是true那么我们获取内部数据,如果用户数据那么获取用户自定义数据,也就是data域下面!thisCache = pvt ? cache[ id ] : cache[ id ].data;if ( thisCache ) {// Support array or space separated string names for data keysif ( !jQuery.isArray( name ) ) {// try the string as a key before any manipulationif ( name in thisCache ) {name = [ name ];} else {// split the camel cased version by spaces unless a key with the spaces existsname = 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 ) );}           //首先把name变成一个数组!把用户自定义数据或者内部数据指定的数据删除!i = name.length;while ( i-- ) {delete thisCache[ name[i] ];}// If there is no data left in the cache, we want to continue// and let the cache object itself get destroyedif ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {return;}}}//如果用户数据那么把data域也删除!// See jQuery.data for more informationif ( !pvt ) {delete cache[ id ].data;// Don't destroy the parent cache unless the internal data object// had been the only thing left in itif ( !isEmptyDataObject( cache[ id ] ) ) {return;}}// Destroy the cache//如果是DOM,那么调用cleanDataif ( 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 ( support.deleteExpando || cache != cache.window ) {/* jshint eqeqeq: true */delete cache[ id ];// When all else fails, null} else {cache[ id ] = null;}}

总结:

jQuery.cleanData在jQuery内部代码中调用频率特别高,要弄懂

0 0
原创粉丝点击