attr()与prop()
$.data()或者$(selector).data()可以向元素上添加数据,类似于attr()或者prop()方法。但是attr()或者prop()方法容易形成循环引用,造成内存泄露。例如:
require(["math","jquery"],function(math,$){
var obj={
dom:$(".test5")[0],
name:"div"
};
$(".test5").attr("name",obj);
});
模块中的代码就形成了一个循环引用。
原理分析
data()数据缓存会在元素上生成一个属性,该属性是
this.expando = jQuery.expando + Data.uid++;
是一个唯一的,属性的值为 数值:1,2,3...,jquery内部会建立一个对象cache用于缓存数据。
例如:$("#div1").data("name","myDiv"),这时,cache[1]="myDIv"
<div id="div1" jQuery2140289804814383387571="1">
由此可见,使用data()数据缓存,不会造成循环引用。
源码分析
构造函数
function Data() {
// Support: Android<4,
// Old WebKit does not have Object.preventExtensions/freeze method,
// return new empty object instead with no [[set]] accessor
Object.defineProperty( this.cache = {}, 0, {
get: function() {
return {};
}
});
this.expando = jQuery.expando + Data.uid++;
}
Data.uid = 1;
Data.accepts = jQuery.acceptData;
Data()构造函数,函数内部定义了cache属性,cache属性用于缓存数据。Object.defineProperty()是ES5中定义的用于生成或者对象属性,这里get方法用于读取属性值,没有set方法说明该属性是只读的。jQuery.expando是jquery版本号加一个随机数,当调用data()时,会向元素添加属性expando。而Data.uid
作为键值来确定是cache中的哪个值。下面看下acceptData()
jQuery.acceptData = function( owner ) {
// Accepts only:
// - Node
// - Node.ELEMENT_NODE
// - Node.DOCUMENT_NODE
// - Object
// - Any
/* jshint -W018 */
return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
};
该函数只有在DOM元素、document和对象是返回true;Data原型上的方法
Data.prototype={
key:function(){//.....}, //用于获取键值
set:function(){//......}, //写入值
get:function(){//......}, //读值
access:function(){//......}, //get set的快捷方式
remove:function(){//......}, //删除cache中key对应的值
hasData:function(){//......}, //判断是否有缓存
discard:function(){} //删除cache中的值包括key
}
key()解析:
key: function( owner ) {
// We can accept data for non-element nodes in modern browsers,
// but we should not, see #8335.
// Always return the key for a frozen object.
if ( !Data.accepts( owner ) ) {//如果不是对象、DOM元素或者document对象,就返回0
return 0;
}
var descriptor = {},
// Check if the owner object already has a cache key
unlock = owner[ this.expando ]; //键值
// If not, create one
if ( !unlock ) { //键值不存在,就创建一个
unlock = Data.uid++;
// Secure it in a non-enumerable, non-writable property
try {
descriptor[ this.expando ] = { value: unlock }; //将对象添加到元素上,同时保证对象不能被修改
Object.defineProperties( owner, descriptor );
// Support: Android<4
// Fallback to a less secure definition
} catch ( e ) { //如果不支持就使用extend
descriptor[ this.expando ] = unlock;
jQuery.extend( owner, descriptor );
}
}
// Ensure the cache object
if ( !this.cache[ unlock ] ) { //确保
this.cache[ unlock ] = {};
}
return unlock; //返回键值
},
set()解析:
</pre><pre name="code" class="javascript">set: function( owner, data, value ) {
var prop,
// There may be an unlock assigned to this node,
// if there is no entry for this "owner", create one inline
// and set the unlock as though an owner entry had always existed
unlock = this.key( owner ),//获取键值
cache = this.cache[ unlock ]; //然后cache引用键值对应的对象
// Handle: [ owner, key, value ] args
if ( typeof data === "string" ) { //单个数据赋值操作
cache[ data ] = value;
// Handle: [ owner, { properties } ] args
} else { //
// Fresh assignments by object are shallow copied
if ( jQuery.isEmptyObject( cache ) ) { //如果cache是空对象,直接利用jq的extend方法扩展
jQuery.extend( this.cache[ unlock ], data );
// Otherwise, copy the properties one-by-one to the cache object
} else { //不是空对象想,则利用for in循环添加。
for ( prop in data ) {
cache[ prop ] = data[ prop ];
}
}
}
return cache;
},
get()方法:
get: function( owner, key ) {
// Either a valid cache is found, or will be created.
// New caches will be created and the unlock returned,
// allowing direct access to the newly created
// empty data object. A valid owner object must be provided.
var cache = this.cache[ this.key( owner ) ]; //根据键值来获取值
return key === undefined ? //如果没有入参,则返回整个缓存
cache : cache[ key ];
},
access()方法:
access: function( owner, key, value ) {
var stored;
// In cases where either:
//
// 1. No key was specified
// 2. A string key was specified, but no value provided
//
// Take the "read" path and allow the get method to determine
// which value to return, respectively either:
//
// 1. The entire cache object
// 2. The data stored at the key
//
if ( key === undefined || //如果键值不存在或者键值存在但是值不存在的情况下,就调用get方法
((key && typeof key === "string") && value === undefined) ) {
stored = this.get( owner, key );
return stored !== undefined ?
stored : this.get( owner, jQuery.camelCase(key) );
}
// [*]When the key is not a string, or both a key and value
// are specified, set or extend (existing objects) with either:
//
// 1. An object of properties
// 2. A key and value
//
this.set( owner, key, value ); //设值
// Since the "set" path can have two possible entry points
// return the expected data based on which path was taken[*]
return value !== undefined ? value : key;
},
remove()方法
remove: function( owner, key ) {
var i, name, camel,
unlock = this.key( owner ),
cache = this.cache[ unlock ];
if ( key === undefined ) { //如果没有传入键值,则删掉所有值
this.cache[ unlock ] = {};
} else {
// Support array or space separated string of keys
if ( jQuery.isArray( key ) ) { //如果传入的是数组(可以删除多个值)
// 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 = key.concat( key.map( jQuery.camelCase ) ); 将key值转换为驼峰写法,加到数组中
} else {
camel = jQuery.camelCase( key ); //删除单个
// Try the string as a key before any manipulation
if ( key in cache ) {
name = [ key, camel ];
} else { //入参不再cache中,则看看驼峰写法有没有,如果还没有,去完空格看看是否匹配,如果还没有,返回空
// If a key with the spaces exists, use it.
// Otherwise, create an array by matching non-whitespace
name = camel;
name = name in cache ?
[ name ] : ( name.match( rnotwhite ) || [] );
}
}
i = name.length;
while ( i-- ) {
delete cache[ name[ i ] ]; //删除
}
}
},
hasData():
hasData: function( owner ) { //如果存在返回true
return !jQuery.isEmptyObject(
this.cache[ owner[ this.expando ] ] || {}
);
},
discard():
discard: function( owner ) {
if ( owner[ this.expando ] ) {
delete this.cache[ owner[ this.expando ] ]; //删除键值对应的对象
}
}
jQ data()工具方法
源码
jQuery.extend({
hasData: function( elem ) {
return data_user.hasData( elem ) || data_priv.hasData( elem ); //调用公开的Data实例或者私有实例上的hasData()方法
},
data: function( elem, name, data ) {
return data_user.access( elem, name, data ); //调用Data实例上的access()方法
},
removeData: function( elem, name ) { //删除元素上的数据缓存
data_user.remove( elem, name );
},
// TODO: Now that all calls to _data and _removeData have been replaced
// with direct calls to data_priv methods, these can be deprecated.
_data: function( elem, name, data ) { //内部使用的的方法
return data_priv.access( elem, name, data );
},
_removeData: function( elem, name ) { //jq内部使用的私有删除方法
data_priv.remove( elem, name );
}
});
实例:
$.data($(".test5")[0],"name","vuturn");
console.log($.data($(".test5")[0],"name")); //vuturn
$.removeData($(".test5")[0],"name");
console.log($.data($(".test5")[0],"name")); //undefined
jq的data()实例方法
jQuery.fn.extend({
data: function( key, value ) {
var i, name, data,
elem = this[ 0 ],
attrs = elem && elem.attributes;
// Gets all values
if ( key === undefined ) { //如果键值不存在
if ( this.length ) { //. 如果选择器的长度大于1
data = data_user.get( elem ); //利用公开的data实例的get方法获取。
if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { //如果是DOM元素,且没有hasDataAttr
i = attrs.length; //DOM元素属性的个数
while ( i-- ) {
// Support: IE11+
// The attrs elements can be null (#14894)
if ( attrs[ i ] ) {
name = attrs[ i ].name;
if ( name.indexOf( "data-" ) === 0 ) {
name = jQuery.camelCase( name.slice(5) ); //遍历看看是否有html5自定义属性
dataAttr( elem, name, data[ name ] );
}
}
}
data_priv.set( elem, "hasDataAttrs", true );
}
}
return data;
}
// Sets multiple values
if ( typeof key === "object" ) { //设多个值
return this.each(function() {
data_user.set( this, key );
});
}
return access( this, function( value ) { //调用access设值
var data,
camelKey = jQuery.camelCase( key );
// The calling jQuery object (element matches) is not empty
// (and therefore has an element appears at this[ 0 ]) and the
// `value` parameter was not undefined. An empty jQuery object
// will result in `undefined` for elem = this[ 0 ] which will
// throw an exception if an attempt to read a data cache is made.
if ( elem && value === undefined ) {//如果value不存在,取值
// Attempt to get data from the cache
// with the key as-is
data = data_user.get( elem, key );
if ( data !== undefined ) {
return data;
}
// Attempt to get data from the cache
// with the key camelized
data = data_user.get( elem, camelKey ); //这时是转换为驼峰写法,继续查找
if ( data !== undefined ) {
return data;
}
// Attempt to "discover" the data in
// HTML5 custom data-* attrs
data = dataAttr( elem, camelKey, undefined ); //如果还找不到,则把自定义属性返回
if ( data !== undefined ) {
return data;
}
// We tried really hard, but the data doesn't exist.
return;
}
// Set the data...
this.each(function() {
// First, attempt to store a copy or reference of any
// data that might've been store with a camelCased key.
var data = data_user.get( this, camelKey );
// For HTML5 data-* attribute interop, we have to
// store property names with dashes in a camelCase form.
// This might not apply to all properties...*
data_user.set( this, camelKey, value );
// *... In the case of properties that might _actually_
// have dashes, we need to also store a copy of that
// unchanged property.
if ( key.indexOf("-") !== -1 && data !== undefined ) {
data_user.set( this, key, value );
}
});
}, null, value, arguments.length > 1, null, true );
},
removeData: function( key ) {
return this.each(function() {
data_user.remove( this, key );
});
}
});