jQuery toggleClass 源码解读
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,此外,它还有一些方法
在调用data_priv.set()时,会首先调用data_priv.key(),因为它会在elem上通过defineProperties()为elem添加一个expano的值
比如:
之后data_priv.set中就可以在data_priv.cache中存储值:
在调用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;//3 }, 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();
hi,我的新博客地址:ysha.me !!