您的位置:首页 > Web前端 > JQuery

jQuery源码学习(版本1.11)-data

2015-05-07 02:13 423 查看

概述

本分详细分析jquery-1.11.1.js源码文件行数:3617~3954;
代码简介:data块代码是jQuery用于缓存数据的代码,两种缓存方式,第一种是对于element和document节点对象,data将其需要缓存的数据缓存在内部缓存jQuery.cache中,element里只保存对应的编号,跟jQuery.cache关联起来,第二种对于其他对象,则直接保存在对象自身里面,jQuery源码多处地方用到data缓存功能,如后面的事件管理,JQ对象绑定的事件及其回调全部缓存在jQuery.cache中;
下文进行详细代码分析

代码分析

/**
* 判断对象是否适合有data缓存
*/
jQuery.acceptData = function( elem ) {
// 从elem.nodeName根据jQuery.noData获取元素,用于整个函数的最后判断
var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ],
nodeType = +elem.nodeType || 1;

// nodeType不为1或9则决不能有缓存,即只有element节点和document节点才能有缓存
return nodeType !== 1 && nodeType !== 9 ?
false :

// noData为空或false则返回true,表明元素可以用缓存data,elem.getAttribute("classid")这项判断用意暂时不理解
!noData || noData !== true && elem.getAttribute("classid") === noData;
};

// 判断是否是json格式的正则
var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
// 用于驼峰转"-[a-z]"的正则
rmultiDash = /([A-Z])/g;

// 本方法是作用是将elem属性转到内部缓存去管理,并且返回,后面用其缓存数据,也用其获取数据
function dataAttr( elem, key, data ) {
// data是缓存对象key属性的引用,如果为undefined则说明需要转换进去管理
if ( data === undefined && elem.nodeType === 1 ) {

// 驼峰转成横杠写法
var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();

// 获取data-开头的属性值
data = elem.getAttribute( name );

// data必须是string才会缓存,否则不缓存
// 根据获取的string值进行不同处理
if ( typeof data === "string" ) {
try {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
// Only convert to a number if it doesn't change the string
+data + "" === data ? +data :
rbrace.test( data ) ? jQuery.parseJSON( data ) :
data;
} catch( e ) {}

// 确保缓存成功
jQuery.data( elem, key, data );

} else {
data = undefined;
}
}

返回缓存值
return data;
}

// 判断缓存对象obj是否为空对象
function isEmptyDataObject( obj ) {
var name;
for ( name in obj ) {

// 外部使用的数据有可能会存在obj.data,因此特别判断obj.data是否为空,为空则要跳过这一个name
if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
continue;
}
// 只有存在name !== "toJSON" ,则说明有缓存数据,函数返回false,
if ( name !== "toJSON" ) {
return false;
}
}

// for循环当中无法返回false,则说明缓存对象为空,则返回true
return true;
}

function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
// 判断elem是否适合使用data缓存
if ( !jQuery.acceptData( elem ) ) {
return;
}

var ret, thisCache,
internalKey = jQuery.expando,

// 判断是否dom节点,dom节点使用jQuery内部缓存间接关联数据,因为ie6,ie7无法对dom节点进行回收
isNode = elem.nodeType,

// 只有dom节点需要是否jQuery内部缓存,js对象直接在其内部缓存即可
cache = isNode ? jQuery.cache : elem,

// 同样只对dom节点定义id(1,2,3...),非dom节点统一使用internalKey
id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;

if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {
return;
}

// id不存在则对其初始化
if ( !id ) {
// 只对dom节点有效
if ( isNode ) {
id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
} else {
id = internalKey;
}
}

// 如果cache[ id ]为空,则进行初始化
if ( !cache[ id ] ) {
cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
}

// name是object或function的场景,直接扩展进cache
if ( typeof name === "object" || typeof name === "function" ) {
// 如果是内部调用,则扩展进cache[ id ],否则扩展进cache[ id ].data,避免数据冲突
if ( pvt ) {
cache[ id ] = jQuery.extend( cache[ id ], name );
} else {
cache[ id ].data = jQuery.extend( cache[ id ].data, name );
}
}

thisCache = cache[ id ];

// 如果没有设置pvt,说明是调用外部使用的工具方法data
// 则增加一层,把数据保存在cache[ id ].data里面,以免与内部使用的数据冲突
if ( !pvt ) {
if ( !thisCache.data ) {
thisCache.data = {};
}
// 设置以下引用后,后面直接操作thisCache即可
thisCache = thisCache.data;
}

// 把data加进缓存中,null也是有效值,因此这里使用三等号判断
if ( data !== undefined ) {
thisCache[ jQuery.camelCase( name ) ] = data;
}

// 获取返回值
if ( typeof name === "string" ) {

// 先直接获取而不是转驼峰,因为可能就是需要获取指定值
ret = thisCache[ name ];

// 不存在则把name转成驼峰写法获取
if ( ret == null ) {

// 返回的ret是name对应的data数据
ret = thisCache[ jQuery.camelCase( name ) ];
}
} else {
// 返回的ret是整个缓存对象
ret = thisCache;
}

return ret;
}

function internalRemoveData( elem, name, pvt ) {
// 判断elem是否适合有缓存
if ( !jQuery.acceptData( elem ) ) {
return;
}

var thisCache, i,
isNode = elem.nodeType,

// 获取对应的缓存以及对应的id
cache = isNode ? jQuery.cache : elem,
id = isNode ? elem[ jQuery.expando ] : jQuery.expando;

// 如果不存在缓存,则删除动作就是无用的,直接返回没必要继续
if ( !cache[ id ] ) {
return;
}

// name不为空进入删除逻辑
if ( name ) {

// 内部使用跟外部使用的是不同层级的缓存,设置好引用thisCache
thisCache = pvt ? cache[ id ] : cache[ id ].data;

if ( thisCache ) {

// 以下if else块的作用是分割name,最终得到一个所有需要删除的key值的数据
// name支持直接传入数组,或使用空格分割的字符串,来达到批删效果
// 判断是否数组或类数组,不是则进入if,是则进入else
if ( !jQuery.isArray( name ) ) {

// 如果确认是thisCache里的key,直接塞进数组
if ( name in thisCache ) {
name = [ name ];
} else {

// 转驼峰后再次判断
name = jQuery.camelCase( name );
if ( name in thisCache ) {
name = [ name ];
// 前面两个判断都不通过,则分割字符串得到数组
} else {
name = name.split(" ");
}
}
} else {
// name是数组或类数组,则直接concat转小写之后的结果,得到数组(两倍于原name数组,前一半是原数据,后一半是转小写后的数据)
name = name.concat( jQuery.map( name, jQuery.camelCase ) );
}

// 循环执行批量删除
i = name.length;
while ( i-- ) {
delete thisCache[ name[i] ];
}

// 再后面的代码是用于消除thisCache缓存的,如果thisCache不为空,则返回,就不执行消除缓存的动作了
if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {
return;
}
}
}

// 先消除外部使用的缓存
if ( !pvt ) {
delete cache[ id ].data;

// 如果内部使用的不为空则返回,不执行后续消除cache[ id ]的代码
if ( !isEmptyDataObject( cache[ id ] ) ) {
return;
}
}

// 消除该对象缓存
if ( isNode ) {
jQuery.cleanData( [ elem ], true );

} else if ( support.deleteExpando || cache != cache.window ) {
delete cache[ id ];

} else {
cache[ id ] = null;
}
}

// 定义数据缓存相关的工具函数或对象
jQuery.extend({
// jQuery的内部缓存对象(拥有多个用途,事件绑定有使用)
cache: {},

// noData是用于保存特殊的不适合有缓存的element
noData: {
"applet ": true,
"embed ": true,
// ...but Flash objects (which have this classid) *can* handle expandos
"object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
},

// 判断elem是否有缓存数据
hasData: function( elem ) {
// dom则直接调用jQuery.cache,根据elem内部存的id标示去获取对应缓存,非dom则直接从elem身上获取缓存
elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
// 返回boolean值
return !!elem && !isEmptyDataObject( elem );
},

// 缓存数据或者获取数据
data: function( elem, name, data ) {
return internalData( elem, name, data );
},

// 删除缓存数据
removeData: function( elem, name ) {
return internalRemoveData( elem, name );
},

// 相当于内部使用的data方法
_data: function( elem, name, data ) {
return internalData( elem, name, data, true );
},

// 相当于内部使用的removeData方法
_removeData: function( elem, name ) {
return internalRemoveData( elem, name, true );
}
});

// JQ原型扩展的data,removeData函数
jQuery.fn.extend({
data: function( key, value ) {
var i, name, data,
// elem设置为JQ对象的第一个数据
elem = this[0],
attrs = elem && elem.attributes;

// key为undefined说明是获取所有缓存数据
if ( key === undefined ) {
// 判断JQ对象的length,我不懂为什么不直接判断elem
if ( this.length ) {
// 获取对应到缓存
data = jQuery.data( elem );

// 以下分支应该是获取节点内部的属性,一并返回
// parsedAttrs应该是用来判断节点内部的属性是否已经转到内部缓存的标志
if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
i = attrs.length;
// 遍历节点的attributes
while ( i-- ) {
if ( attrs[ i ] ) {
name = attrs[ i ].name;
// 如果是以data-为开头的,则需要转到缓存去保存
if ( name.indexOf( "data-" ) === 0 ) {
// 驼峰第5位后的字符串
name = jQuery.camelCase( name.slice(5) );
// 调用dataAttr缓存属性
dataAttr( elem, name, data[ name ] );
}
}
}
// 设置parsedAttrs,下次调用就不会再进入上面分支了
jQuery._data( elem, "parsedAttrs", true );
}
}

// 将获取到的缓存对象返回
return data;
}

// 如果key是object,则调用each,会对JQ对象里的每一个元素都执行callback
// callback里执行jQuery.data( this, key );即将key缓存进每一个元素的缓存中
if ( typeof key === "object" ) {
return this.each(function() {
jQuery.data( this, key );
});
}

return arguments.length > 1 ?

// 缓存数据,JQ对象的每个元素都会缓存
// this.each返回的是JQ对象
this.each(function() {
jQuery.data( this, key, value );
}) :

// 调用dataAttr获取属性,并且将属性缓存起来
elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
},

// JQ对象的删除缓存的方法,调用each对每一个元素生效
removeData: function( key ) {
return this.each(function() {
jQuery.removeData( this, key );
});
}
});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: