jQuery源码进修之data
jQuery中有两个关于data操纵的要领
$().data()
$.data(elem);
内部其完成均离不开自定义类Data
内部类 Data
Data
在src/data/Data.js
定义,构建时为实例增加expando
属性,作为唯一标识
function Data() {
this.expando = jQuery.expando + Data.uid++;
}
在原型上增加了多个要领
Data.prototype = {
cache: function(){
...
},
set: function(){
...
},
get: function(){
...
},
access: function(){
...
},
remove: function(){
...
},
hasData: function(){
...
}
}
在jq内部,运用cache要领猎取缓存的数据。传入一个参数owner,示意要猎取缓存数据的对象。推断在owner上是不是有expando属性,假如没有,申明这个owner是不是第一次挪用,须要在其初始化缓存数据对象。推断节点的范例,假如是元素节点或许document节点或许对象时,能够设置缓存数据。假如是元素节点或许document节点,直接运用对象字面量举行赋值,属性名是expando,值为空对象。假如是对象的话,运用Object.defineProperty
为其定义数据,属性名也是expando,初始化为{},同时属性描述符能够变动,不可枚举。
Data.prototype.cache
cache: function( owner ) {
// Check if the owner object already has a cache
// 猎取在owner的缓存值
var value = owner[ this.expando ];
// If not, create one
if ( !value ) {
value = {};
// We can accept data for non-element nodes in modern browsers,
// but we should not, see #8335.
// Always return an empty object.
// 推断owener范例 是不是能在其上挪用data
// 在元素节点或body或对象上能够设置data
// 其他节点不设置缓存数据
if ( acceptData( owner ) ) {
// If it is a node unlikely to be stringify-ed or looped over
// use plain assignment
// 此处为owner增加属性 key为Data对象的expando值 竖立owner和Data对象之间的衔接
// owner是元素节点或body
if ( owner.nodeType ) {
owner[ this.expando ] = value;
// Otherwise secure it in a non-enumerable property
// configurable must be true to allow the property to be
// deleted when data is removed
// owner是对象
// 为owner增加expando属性 初始化为{},同时属性描述符能够变动,不可枚举
} else {
Object.defineProperty( owner, this.expando, {
value: value,
configurable: true
} );
}
}
}
return value;
}
运用set
来更新缓存对象,分为data(key,value)
或data(obj)
两种挪用状况,保留时要将键名保留为驼峰定名法。
Data.prototype.set
set: function( owner, data, value ) {
var prop,
cache = this.cache( owner );
// Handle: [ owner, key, value ] args
// Always use camelCase key (gh-2257)
if ( typeof data === "string" ) {
cache[ jQuery.camelCase( data ) ] = value;
// Handle: [ owner, { properties } ] args
} else {
// Copy the properties one-by-one to the cache object
for ( prop in data ) {
cache[ jQuery.camelCase( prop ) ] = data[ prop ];
}
}
return cache;
}
运用get
来猎取缓存对象,挪用时有data(key)
和data()
。未指定key时直接返回全部cache对象,不然返回cache[key]。键名也要转为驼峰定名。
Data.prototype.get
get: function( owner, key ) {
return key === undefined ?
this.cache( owner ) :
// Always use camelCase key (gh-2257)
owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];
}
对挪用的体式格局举行辨别,内部挪用 set
或get
经由过程参数的数目和范例举行辨别:
- key为空时,猎取全部cache对象
- key范例为
string
且value===undefined
对应猎取指定值 - 其他挪用均为
set
,在set
内部举行辨别
Data.prototype.access
access: function( owner, key, value ) {
// 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 ||
( ( key && typeof key === "string" ) && value === undefined ) ) {
return this.get( owner, 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
来删除缓存对象属性,挪用时,能够传入一个string,示意要删除的键名,或许传入一个保留多个键名的string数组。键名也要转为驼峰定名。假如不传入出参数,则直接删除掉在owner上的缓存数据对象。
Data.prototype.remove
remove: function( owner, key ) {
var i,
cache = owner[ this.expando ];
if ( cache === undefined ) {
return;
}
if ( key !== undefined ) {
// Support array or space separated string of keys
if ( Array.isArray( key ) ) {
// If key is an array of keys...
// We always set camelCase keys, so remove that.
key = key.map( jQuery.camelCase );
} else {
key = jQuery.camelCase( key );
// If a key with the spaces exists, use it.
// Otherwise, create an array by matching non-whitespace
key = key in cache ?
[ key ] :
( key.match( rnothtmlwhite ) || [] );
}
i = key.length;
while ( i-- ) {
delete cache[ key[ i ] ];
}
}
// Remove the expando if there's no more data
if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
// Support: Chrome <=35 - 45
// Webkit & Blink performance suffers when deleting properties
// from DOM nodes, so set to undefined instead
// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
if ( owner.nodeType ) {
owner[ this.expando ] = undefined;
} else {
delete owner[ this.expando ];
}
}
}
推断owner上是不是有缓存数据。
Data.prototype.hasData
hasData: function( owner ) {
var cache = owner[ this.expando ];
return cache !== undefined && !jQuery.isEmptyObject( cache );
}
jQuery要领的定义
定义后内部类data
后,在/src/data.js
举行拓展。在jQuery增加了hasData
、data
、 removeData
、_data
、_removeData
等要领,在jQuery.fn上增加了data
和removeData
要领。
在jQuery拓展的要领,都是对Data要领的封装,在挪用时$.data()
时,并没有对set
和get
操纵辨别,在Data.prototype.access
内部辨别set和get。下面源码中,dataUser
和dataPriv
是Data
实例,分别为缓存数据和示意jq对象是不是将元素的data属性增加到dataUser
中
// $.data
jQuery.extend( {
hasData: function( elem ) {
return dataUser.hasData( elem ) || dataPriv.hasData( elem );
},
data: function( elem, name, data ) {
return dataUser.access( elem, name, data );
},
removeData: function( elem, name ) {
dataUser.remove( elem, name );
},
// TODO: Now that all calls to _data and _removeData have been replaced
// with direct calls to dataPriv methods, these can be deprecated.
_data: function( elem, name, data ) {
return dataPriv.access( elem, name, data );
},
_removeData: function( elem, name ) {
dataPriv.remove( elem, name );
}
} );
在jQuery.fn
上拓展的要领只要data
和removeData
。在data
内,先对$().data()
和$().data({k:v})
两个挪用状况举行处置惩罚。假如是第一种状况,则返回在this[0]
上的缓存数据对象,假如是第一次以$().data()
的体式格局挪用,同时还会将元素上的data属性增加dataUser
中,并更新dataPriv
。假如是$().data({k:v})
的挪用体式格局,则遍历jq对象,为每一个节点更新缓存数据。其他挪用体式格局如$().data(k)
和$().data(k,v)
则挪用access
举行处置惩罚。此处的access
并不是Data.prototype.access
。
removeData
则遍历jq对象,删除在每一个节点上的缓存数据。
// $().data
jQuery.fn.extend( {
data: function( key, value ) {
var i, name, data,
elem = this[ 0 ], // elem为dom对象
attrs = elem && elem.attributes; // 节点上的属性
// Gets all values
// $().data()
if ( key === undefined ) {
if ( this.length ) {
data = dataUser.get( elem );
// elem是元素节点,且dataPriv中无hasDataAttrs时实行这个代码块里的代码
// dataPriv上的hasDataAttrs示意elem是不是有data-xxx属性
// 初始化dataPriv后花括号内的代码不再实行,即以下的if内的代码只实行一次
if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
i = attrs.length;
while ( i-- ) {
// Support: IE 11 only
// The attrs elements can be null (#14894)
// 将elem的data-*-*属性以*-*转为驼峰定名体式格局的值为键
// 在dataAttr函数内保留到dataUser中
// 所以用$().data能够猎取元素的data-*属性 但修改后dom上的属性却不变化
if ( attrs[ i ] ) {
name = attrs[ i ].name;
// name为data-xxx
if ( name.indexOf( "data-" ) === 0 ) {
// name为xxx
name = jQuery.camelCase( name.slice( 5 ) );
dataAttr( elem, name, data[ name ] );
}
}
}
// 同时将dataPriv的hasDataAttrs属性设置为真,示意已将元素属性节点上的data属性保留到缓存对象中
dataPriv.set( elem, "hasDataAttrs", true );
}
}
return data;
}
// Sets multiple values
// $().data(obj) 此处遍历this,即遍历jq对象上一切的节点,并在其设置值
if ( typeof key === "object" ) {
return this.each( function() {
dataUser.set( this, key );
} );
}
// 除了$().data()和$().data({k:v})的其他状况
return access( this, function( value ) {
var data;
// 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.
// value undefined申明是猎取操纵
// 挪用$().data(k)
if ( elem && value === undefined ) {
// Attempt to get data from the cache
// The key will always be camelCased in Data
// 从dataUser中猎取 非undefined时返回
data = dataUser.get( elem, key );
if ( data !== undefined ) {
return data;
}
// Attempt to "discover" the data in
// HTML5 custom data-* attrs
// dataUser中不存在key,挪用dataAttr查找元素的data-*属性
// 假如存在属性,更新dataUser并返回其值
data = dataAttr( elem, key );
if ( data !== undefined ) {
return data;
}
// We tried really hard, but the data doesn't exist.
return;
}
// Set the data...
// jq对象长度 >= 1, 挪用如$().data(k,v) 遍历jq对象,为每一个节点设置缓存数据
this.each( function() {
// We always store the camelCased key
dataUser.set( this, key, value );
} );
}, null, value, arguments.length > 1, null, true );
},
// 遍历jq对象,删除各个元素上的缓存数据
removeData: function( key ) {
return this.each( function() {
dataUser.remove( this, key );
} );
}
} );
个中,getData
用于对元素上的data属性举行范例转换,dataAttr
用于猎取保留在元素节点上的data属性,并同时更新dataUser
。须要注重的是,以$().data(k, v)
体式格局挪用时,假如在缓存数据上查找不到属性,则会挪用dataAttr
在元素查找属性。
// 属性值是string 举行范例转换
function getData( data ) {
if ( data === "true" ) {
return true;
}
if ( data === "false" ) {
return false;
}
if ( data === "null" ) {
return null;
}
// Only convert to a number if it doesn't change the string
// data转化成number再转成string后仍严厉即是data
if ( data === +data + "" ) {
return +data;
}
if ( rbrace.test( data ) ) {
return JSON.parse( data );
}
return data;
}
// 猎取元素的dataset中的属性,并保留到dataUser中
function dataAttr( elem, key, data ) {
var name;
// If nothing was found internally, try to fetch any
// data from the HTML5 data-* attribute
// 此处猎取dataset里的值
if ( data === undefined && elem.nodeType === 1 ) {
name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); // 此处将驼峰定名体式格局的key转化为data-*-*
data = elem.getAttribute( name );
if ( typeof data === "string" ) {
try {
data = getData( data );
} catch ( e ) {}
// Make sure we set the data so it isn't changed later
// 将元素的data-*属性保留到dataUser中
dataUser.set( elem, key, data );
} else {
data = undefined;
}
}
return data;
}