toggleClass: function( value, stateVal ) {
var type = typeof value;//值类型 if ( typeof stateVal === "boolean" && type === "string" ) {//如果第二个参数为bool值
return stateVal ? this.addClass( value ) : this.removeClass( value );//如果第二个参为true,添加class,否则移除class
} if ( jQuery.isFunction( value ) ) {//如果第一个参数是函数
return this.each(function( i ) {
jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
});
} return this.each(function() {
if ( type === "string" ) {
// toggle individual class names
var className,
i = 0,
self = jQuery( this ),
classNames = value.match( rnotwhite ) || [];//将按空格分隔的className分割成数组 while ( (className = classNames[ i++ ]) ) {//遍历数组
// check each className given, space separated list
if ( self.hasClass( className ) ) {
self.removeClass( className );
} else {
self.addClass( className );
}
} // Toggle whole class name 如果没有传入第一个参数或者第一个参数是undefined或者第一个参数是bool值
} else if ( type === strundefined || type === "boolean" ) {
if ( this.className ) {
// store className if set
data_priv.set( this, "__className__", this.className );
} // If the element has a class name or if we're passed "false",
// then remove the whole classname (if there was one, the above saved it).
// Otherwise bring back whatever was previously saved (if anything),
// falling back to the empty string if nothing was stored.
this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
}
});
}

前面几种情况都很好理解,最后一种$("#xxx").toggleClass()则用到了data_priv对象,因为它需要将移除的className缓存起来。

data_priv是jQuery内部的缓存对象,在初始化时,它会生成一个空的cache对象用于缓存数据,还有根据jQuery.expando生成它自身的expando,此外,它还有一些方法

jQuery toggleClass 源码解读-LMLPHP

在调用data_priv.set()时,会首先调用data_priv.key(),因为它会在elem上通过defineProperties()为elem添加一个expano的值

比如:jQuery toggleClass 源码解读-LMLPHP

之后data_priv.set中就可以在data_priv.cache中存储值:

jQuery toggleClass 源码解读-LMLPHP

在调用data_priv.get()时,首先也是调用data_priv.key()方法,用它来返回elem中expando的值,也就是cache中对应的键,然后就能找到存储的值了。

data_priv的源码如下:

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, {//初始化定义this.cache为{}
get: function() {
return {};
}
});
this.expando = jQuery.expando + Data.uid++;//jQuery21301509336824528871
} Data.uid = 1;
Data.accepts = jQuery.acceptData; Data.prototype = {
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 ) ) {
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++;//3 // Secure it in a non-enumerable, non-writable property
try {
descriptor[ this.expando ] = { value: unlock };
Object.defineProperties( owner, descriptor );
//console.log(owner[this.expando]);//3 // Support: Android<4
// Fallback to a less secure definition
} catch ( e ) {
descriptor[ this.expando ] = unlock;
jQuery.extend( owner, descriptor );
}
} // Ensure the cache object if ( !this.cache[ unlock ] ) {//undefined
this.cache[ unlock ] = {};
}
return unlock;//
},
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 ),//3
cache = this.cache[ unlock ];//{} // 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 ) ) {
jQuery.extend( this.cache[ unlock ], data );
// Otherwise, copy the properties one-by-one to the cache object
} else {
for ( prop in data ) {
cache[ prop ] = data[ prop ];
}
}
}
return cache;
},
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: 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 ||
((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: 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 ) );
} else {
camel = jQuery.camelCase( key );
// Try the string as a key before any manipulation
if ( key in cache ) {
name = [ key, camel ];
} else {
// 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: function( owner ) {
return !jQuery.isEmptyObject(
this.cache[ owner[ this.expando ] ] || {}
);
},
discard: function( owner ) {
if ( owner[ this.expando ] ) {
delete this.cache[ owner[ this.expando ] ];
}
}
};
var data_priv = new Data();
var data_user = new Data();
04-15 11:38