// 代码行:3915——3972
// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
var i = 0,
len = elems.length,
bulk = key == null;
// Sets many values
if ( toType( key ) === "object" ) {
chainable = true;
for ( i in key ) {
access( elems, fn, i, key[ i ], true, emptyGet, raw );
}
// Sets one value
} else if ( value !== undefined ) {
chainable = true;
if ( !isFunction( value ) ) {
raw = true;
}
if ( bulk ) {
// Bulk operations run against the entire set
if ( raw ) {
fn.call( elems, value );
fn = null;
// ...except when executing function values
} else {
bulk = fn;
fn = function( elem, key, value ) {
return bulk.call( jQuery( elem ), value );
};
}
}
if ( fn ) {
for ( ; i < len; i++ ) {
fn(
elems[ i ], key, raw ?
value :
value.call( elems[ i ], i, fn( elems[ i ], key ) )
);
}
}
}
if ( chainable ) {
return elems;
}
// Gets
if ( bulk ) {
return fn.call( elems );
}
return len ? fn( elems[ 0 ], key ) : emptyGet;
};
// 代码行:4004——4158
function Data() {
// 设定唯一标识
this.expando = jQuery.expando + Data.uid++;
}
Data.uid = 1;
Data.prototype = {
// 建立一个cache
cache: function( owner ) {
// Check if the owner object already has a cache
// 检查所有者对象是否已经拥有缓存
var value = owner[ this.expando ];
// If not, create one
// 如果没有value,则创建一个
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.
// 我们可以在现代浏览器中接受非元素节点的数据,但是我们不应该这么做,总是返回一个空对象。
if ( acceptData( owner ) ) {
// If it is a node unlikely to be stringify-ed or looped over
// use plain assignment
//如果它是一个不太可能被字符串化或循环的节点,使用简单的赋值
// 判断owner是一个合格者后
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
//否则,将其保护在一个不可枚举的属性中
//可配置的必须为true才能允许属性为true
//删除数据时删除
} else {
Object.defineProperty( owner, this.expando, {
value: value,
configurable: true
} );
}
}
}
return value;
},
// set()用于dom设置key和value
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[ camelCase( data ) ] = value;
// Handle: [ owner, { properties } ] args
// 处理data为这种情况:[ owner, { properties } ]
} else {
// Copy the properties one-by-one to the cache object
// 将属性逐个复制到缓存对象
for ( prop in data ) {
cache[ camelCase( prop ) ] = data[ prop ];
}
}
return cache;
},
get: function( owner, key ) {
return key === undefined ?
this.cache( owner ) :
// Always use camelCase key (gh-2257)
owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
},
// 用来访问,将get、set结合到一起,并对underfined判断
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
//
//在下列任何一种情况下:1.没有指定密钥。2.指定了字符串键,但没有提供值。选择“read”路径并允许get方法确定,分别返回哪个值:1.整个缓存对象 2.存储在键上的数据
// 如果key是undefined或key是字符串、data是undefined说明实在读取数据
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
//
//当键不是字符串,或者键和值都不是时。指定、设置或扩展(现有对象)时,1.属性的对象。2.键和值
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[*]
// 因为“set”路径可以有两个可能的入口点
// 返回所选择路径的期望数据[*]
return value !== undefined ? value : key;
},
// 用于移除cache
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
// 支持删除数组格式的key
if ( Array.isArray( key ) ) {
// If key is an array of keys...
// We always set camelCase keys, so remove that.
key = key.map( camelCase );
} else {
// 为了保持一致,强行的构造了一个数组
key = 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
// cache为空的时候,删除整个缓存
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 ];
}
}
},
hasData: function( owner ) {
var cache = owner[ this.expando ];
return cache !== undefined && !jQuery.isEmptyObject( cache );
}
};
var dataPriv = new Data();
var dataUser = new Data();
// Implementation Summary
//
// 1. Enforce API surface and semantic compatibility with 1.9.x branch
// 2. Improve the module's maintainability by reducing the storage
// paths to a single mechanism.
// 3. Use the same single mechanism to support "private" and "user" data.
// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
// 5. Avoid exposing implementation details on user objects (eg. expando properties)
// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
// 代码行:4172——4221
var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
rmultiDash = /[A-Z]/g;
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
if ( data === +data + "" ) {
return +data;
}
if ( rbrace.test( data ) ) {
return JSON.parse( data );
}
return data;
}
// 函数dataAttr(elem, key, data)解析HTML5属性data-中含有的数据,并把解析结果放入DOM元素关联的自定义数据缓存对象中。
function dataAttr( elem, key, data ) {
// 参数elem:表示待解析HTML5属性data-的DOM元素。
// 参数key:表示待解析的数据名,不包含前缀data-。
// 参数data:表示从DOM元素关联的自定义数据缓存对象中取到的数据,只有参数data为undefined时,才会解析HTML5属性data-,即优先读取自定义数据缓存对象中的数据。
var name;
// If nothing was found internally, try to fetch any
// data from the HTML5 data-* attribute
// 如果参数data为undefined,即该DOM元素关联的自定义数据缓存对象中没有指定名称的数据,才会尝试从HTML5属性data-中解析数据,并把尝试把解析结果转换为合适的JavaScript类型,最后把解析结果放入该DOM元素关联的自定义数据缓存对象中。
if ( data === undefined && elem.nodeType === 1 ) {
// 增加前缀“data-”,并把可能的驼峰式参数name转换为连字符式,然后调用方法elem.getAttribute(name)读取对应的HTML属性,把返回值赋值给变量data。方法elem.getAttribute(name)用于返回指定属性的字符串值。
name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
data = elem.getAttribute( name );
// 如果方法elem.getAttribute(name)有返回值,则尝试把字符串类型的返回值转换为合适的JavaScript类型,包括布尔值、null、数值、对象、数组。
if ( typeof data === "string" ) {
try {
data = getData( data );
} catch ( e ) {}
// Make sure we set the data so it isn't changed later
// 调用方法dataUser.set(elem, key, data)把HTML5属性data-的解析结果放入关联的自定义数据缓存对象jQuery.cache[id].data中
dataUser.set( elem, key, data );
// 如果方法elem.getAttribute(name)的返回值不是字符串,即返回值时null,则修正为undefined。
} else {
data = undefined;
}
}
// 返回属性data-的解析结果。如果参数data不是undefined,则不会解析属性data-,而是直接返回参数data。
return data;
}
// 代码行:4247——4328
jQuery.fn.extend( {
// 代码行:4248——4321
// 方法.data(key, value)用于为匹配元素设置或读取自定义数据,解析HTML5属性data-,并触发相应的自定义事件getData、setData、changeData。
data: function( key, value ) {
// 参数key:表示要设置或读取的数据名,或者是含有键值对的对象。
// 参数value:表示要设置的数据值,可以是任意类型。
var i, name, data,
elem = this[ 0 ],
attrs = elem && elem.attributes;
// Gets all values
// 如果为传入参数,即参数格式是.data(),则返回第一个匹配元素关联的自定义数据缓存(即获全部数据),包括HTML5属性data-中的数据。
if ( key === undefined ) {
if ( this.length ) {
// 调用方法dataUser.get()获取第一个匹配元素关联的自定义数据缓存对象,并返回。
data = dataUser.get( elem );
// 如果未解析第一个匹配元素的HTML5属性data-,则调用函数dataAttr()解析属性data-中含有的数据,并把解析结果放入关联的自定义数据缓存对象中。解析完成后会设置自定义数据hasDataAttrs为true,表示已经为该元素解析过属性data-。
if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
i = attrs.length;
while ( i-- ) {
// Support: IE 11 only
// The attrs elements can be null (#14894)
// attrs元素可以为空
if ( attrs[ i ] ) {
name = attrs[ i ].name;
if ( name.indexOf( "data-" ) === 0 ) {
name = camelCase( name.slice( 5 ) );
dataAttr( elem, name, data[ name ] );
}
}
}
dataPriv.set( elem, "hasDataAttrs", true );
}
}
return data;
}
// Sets multiple values
// 如果参数key是对象,即参数格式是.data(obj),则遍历匹配元素集合,为每个匹配元素调用方法dataUser.set()批量设置数据。
if ( typeof key === "object" ) {
return this.each( function() {
dataUser.set( this, key );
} );
}
// 否则调用access()
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.
if ( elem && value === undefined ) {
// Attempt to get data from the cache
// The key will always be camelCased in Data
// 尝试从缓存中获取数据
// 密钥将始终在数据中使用驼峰格式
data = dataUser.get( elem, key );
if ( data !== undefined ) {
return data;
}
// Attempt to "discover" the data in
// HTML5 custom data-* attrs
data = dataAttr( elem, key );
if ( data !== undefined ) {
return data;
}
// We tried really hard, but the data doesn't exist.
return;
}
// Set the data...
this.each( function() {
// We always store the camelCased key
dataUser.set( this, key, value );
} );
}, null, value, arguments.length > 1, null, true );
}
} );