五、数据缓存Data3——.data(key, value)

坚强是说给别人听的谎言 提交于 2020-03-18 03:50:56
// 代码行: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 );
    }
} );

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!