jQuery源码笔记.jpg

jQuery源码 数据缓存

2020-04-15  本文已影响0人  柠檬果然酸

内存泄露的几种情况
1.循环引用
2.Javascript闭包
3.DOM插入

为了避免内存泄漏,最好不要直接在DOM元素上绑定数据,因为这样会造成
1.循环引用
2.直接暴露数据,安全性?
3.增加一堆的自定义属性标签,对浏览器来说是没意的
4.取数据的时候要对HTML节点做操作

为解决以上问题,jQuery提供数据缓存接口

jQuery.data( element, key, value )
.data( )

静态与实例方法的区别
1.jQuery.data()可以实现为dom元素或js对象添加缓存
2.$("ele").data()实是对前者的扩展,其目的是可以方便的通过选择器为多个dom元素添加缓存数据

jQuery缓存的设计思路
数据缓存,jQuery现在支持两种
1.dom元素,数据存储在jQuery.cache中。
2.普通js对象,数据存储在该对象中。

首先先要在内存中开辟一个区域,用来保存数据,jQuery用cache对象{},那么所有的数据无法就是针对cache的CURD操作了。

1:如果是DOM元素,通过分配一个唯一的关联id把DOM元素和该DOM元素的数据缓存对象关联起来,关联id被附加到以jQuery.expando的值命名的属性上,数据存储在全局缓存对象jQuery.cache中。在读取、设置、移除数据时,将通过关联id从全局缓存对象jQuery.cache中找到关联的数据缓存对象,然后在数据缓存对象上执行读取、设置、移除操作。

2:如果是Javascript对象,数据则直接存储在该Javascript对象的属性jQuery.expando上。在读取、设置、移除数据时,实际上是对Javascript对象的数据缓存对象执行读取、设置、移除操作。

3:为了避免jQuery内部使用的数据和用户自定义的数据发生冲突,数据缓存模块把内部数据存储在数据缓存对象上,把自定义数据存储在数据缓存对象的属性data上。

$().data

// 数据缓存模块
jQuery.fn.extend( {
    data: function( key, value ) {
        var i, name, data,
            elem = this[ 0 ],
            attrs = elem && elem.attributes;

        // Gets all values
        // 如果没有传入key,那就获取所有的value
        if ( key === undefined ) {
            if ( this.length ) {
                data = dataUser.get( elem );

                if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
                    i = attrs.length;
                    while ( i-- ) {

                        // Support: IE 11 only
                        // The attrs elements can be null (#14894)
                        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
        // 设置多个值
        // 像$().data( { key1: value1, key2: value2, ... } )这种情况
        if ( typeof key === "object" ) {
            return this.each( function() {
                dataUser.set( this, key );
            } );
        }

        // 其实我没明白access有什么用
        // 传入的回调方法其实已经足够处理设置值和传入值这两种情况了
        // 而且调用access时也没传入key
        // arguments.length > 1:因为$().data()这个接口有两个形参,一个是key,一个是value。如果只有一个参数的话那就是只读取,否则就是设置值
        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 );
    },

    removeData: function( key ) {
        return this.each( function() {
            dataUser.remove( this, key );
        } );
    }
} );

jQuery.access
jQuery的方法设计大都是多用的,可以根据传递参数的个数判断是set还是get处理,不仅如此jQuery还对参数的传递类型还抽出了一个处理的方法jQuery.access,我们可以传递字符串、数组、对象等等,根据这种类型自动分解成接口所有能接受的参数。

通过access解析后的参数就能让dataUser接口所接收,此时我们可以调用数据对象接口开始对数据进行存储设置了。

/**
 * Multifunctional method to get and set values of a collection
 * The value/s can optionally be executed if it's a function
 * @method access
 * @param {参数类型} elems      元素集合
 * @param {参数类型} fn         回调
 * @param {参数类型} key        键
 * @param {参数类型} value      值
 * @param {参数类型} chainable  读取false 设置true
 * @param {参数类型} emptyGet   该参数一般是不给的,当没有元素时返回undefined
 * @param {参数类型} raw        函数false 字符串true
 */
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
    var i = 0,
        len = elems.length,
        bulk = key == null; // key为null时bulk为true

    // 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;

        // value如果不为函数,raw设置为true
        if ( !isFunction( value ) ) {
            raw = true;
        }

        // key为空
        if ( bulk ) {

            // Bulk operations run against the entire set
            // 如果value为字符串直接使用回调函数进行处理,之后不再处理
            if ( raw ) {
                fn.call( elems, value );
                fn = null;

            // ...except when executing function values
            // 如果value为函数会在这里针对性的包装后在下边的if进行处理
            } else {
                bulk = fn;
                fn = function( elem, key, value ) {
                    return bulk.call( jQuery( elem ), value );
                };
            }
        }

        // 使用各函数的回调作为统一处理
        if ( fn ) {
            for ( ; i < len; i++ ) {
                fn(
                    elems[ i ],
                    key,
                    // value为字符串:value
                    // value为函数:value.call( elems[ i ], i, fn( elems[ i ], key ) )
                    raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) )
                );
            }
        }
    }

    // 如果是set功能则返回elems本身
    if ( chainable ) {
        return elems;
    }

    // Gets
    // 否则为get功能:假如使用的是text(),html()方法则直接返回文本值
    if ( bulk ) {
        return fn.call( elems );
    }

    // 否则的话返回第一个元素的参数值。其他情况直接返回undefined(比如元素未找到)
    return len ? fn( elems[ 0 ], key ) : emptyGet;
};

但经过我的多次调试,我发现access接口什么事儿都没做,然后就把参数原封不动的交给回调函数执行。当然这可能仅仅是.data()这个方法中没有处理的必要,但是在其他的.attr()、.html()接口中参数是要经过处理再传给回调函数。

Data类
从源码中可以看到回调函数中是通过data = dataUser.get( elem, key );来取值的,通过dataUser.set( this, key, value );来设置值。这个dataUser哪来的?

/**
 * 用于构建缓存池
 * @class Data
 * @constructor
 */
function Data() {
    this.expando = jQuery.expando + Data.uid++;
}

Data.uid = 1;

Data.prototype = {

    cache: function( owner ) {

        // Check if the owner object already has a cache
        // this.expando在每个页面是唯一的
        // 获取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.
            if ( acceptData( owner ) ) {

                // If it is a node unlikely to be stringify-ed or looped over
                // use plain assignment
                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
                } else {
                    Object.defineProperty( owner, this.expando, {
                        value: value,
                        configurable: true
                    } );
                }
            }
        }

        return 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
        // 设置多个值
        } 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 ) ];
    },
    access: function( owner, key, value ) { // ... },
    remove: function( owner, key ) { // ... },
    hasData: function( owner ) { // ... }
};
var dataPriv = new Data();

var dataUser = new Data();

静态与实例方法的区别

实例方法
静态方法
区别就是实例方法是在DOM对象上开辟一个缓存池,而静态方法是在jQuery对象上开辟一个缓存池。

个人思路
jQuery的这个做法还是将数据绑定在DOM对象上,感觉不算特别好。我个认为更好的做法是:
1.在DOM对象上绑定一个唯一标识符

div#arron = {
    ...
    uniqueid: '1234567890'
}

2.在jQuery静态区域开辟一个缓存池

jQuery.cache = {}

3.在jQuery缓存池中开辟一个属于div#arron的缓存区域

jQuery.cache = {
    1234567890: {}
}

4.在DOM对象上绑定数据

jQuery.cache = {
    1234567890: {
        a: '1',
        b: '2'
    }
}
上一篇 下一篇

猜你喜欢

热点阅读