zoukankan      html  css  js  c++  java
  • jQuery data

    大家会如何设计一个缓存呢?

    一个简单的Cache

    (function(){
    
    var __cache = {},
        Cache = {
            get: function(__name){
                return __cache[__name] || undefined;
            },    
            set: function(__name, __value){
                return (__cache[__name] = __value)
            }
        };
        
    this.Cache = Cache;
    })();
    
    alert(Cache.get("name"));    //undefined
    Cache.set("name", "Bob");
    alert(Cache.get("name"));    //Bob

    但这不是jQuery想要的

    jQuery要解决的是对应元素的缓存数据。

    例如,我们用document.getElementById获得了一个元素element,然后有一个对应的参数value的属性名是key,那么我们想保存到缓存里,那么我们需要告诉缓存element、key、value才能保存数据,而想要获得这个值,则要告诉缓存element和key,才能得到value。

    所以jQuery的缓存实际上是直接绑定到对象中的。

    为什么?因为这样简单啊。

    用上面的方法,先要将element转成字符串或者数字对应缓存里的对象,然后再用该对象来缓存不同key的value……这……太……麻……烦……了!!

    实际上,由于Javascript没有Hash值方法,所以对象转字符串或数字并没有太好的方法,当然绑一个ID在元素上除外。

    做一个别人一般不会用的令牌

    但是绑定在对象上有一个问题,如果属性名用什么呢?

    如果这个属性名别人也拿去用就悲剧了,比如我用.cache绑定数据,但是另一个库也有.cache来绑定数据,就……

    所以,jQuery做了一个正常情况下别人不会用的令牌。

    jQuery.expando = "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" );

    replace函数将core_verision中的非数字全部替换掉,所以最后这个令牌是一个jQuery后面加一个随机数,比如:

      jQuery20018518865841457738

    jQuery.hasData

    jQuery.hasData = function( elem ) {
        elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
        return !!elem && !isEmptyDataObject( elem );
    };

    从这个函数可以看出,如果elem是DOM Element对象,则数据存在jQuery.cahe中,否则存在存在elem对象中。

    jQuery.data & jQuery.removeData

    jQuery.data = function( elem, name, data ) {
        return internalData( elem, name, data, false );
    };
    jQuery.removeData = function( elem, name ) {
        return internalRemoveData( elem, name, false );
    };

    他们分别调用了internalData和internalRemoveData。

    注意专用接口jQuery._data和jQuery._removeData传的最后一个值有些不同。

    这个后面会说到。

    jQuery._data = function( elem, name, data ) {
        return internalData( elem, name, data, true );
    };
    jQuery._removeData = function( elem, name ) {
        return internalRemoveData( elem, name, true );
    };

    internalData

    function internalData( elem, name, data, pvt /* Internal Use Only */ ){
        // 判断该对象能不能绑定数据
        if ( !jQuery.acceptData( elem ) ) {
            return;
        }
    
        var thisCache, ret,
            internalKey = jQuery.expando,
            getByName = typeof name === "string",
    
            // 由于IE6-7的DOM节点引用的垃圾回收问题,需要分开处理DOM节点和JS对象
            // 真心想吐槽,这不是jQuery 2.0么!!!不是说不支持IE6-8么!!!
            isNode = elem.nodeType,
    
            // 如果是DOM节点,则使用jQuery.cache存储数据,否则使用elem本身
            cache = isNode ? jQuery.cache : elem,
    
            // 得到对象的ID号,如果是DOM节点则是其以令牌为属性名的属性值,否则是令牌
            id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
    
        // 避免为了从一个根本没有数据的对象获取数据而浪费时间
        if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
            return;
        }
    
        // 如果没有ID
        if ( !id ) {
            // 如果是DOM节点,就给他一个ID
            if ( isNode ) {
                elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++;
            // 否则以令牌作为其ID
            } else {
                id = internalKey;
            }
        }
    
        // 如果对应ID的缓存不存在
        if ( !cache[ id ] ) {
            // 初始化缓存
            cache[ id ] = {};
    
            // 避免对JS对象使用JSON.stringify时暴露jQuery的元数据对象,所以给对象添加toJSON方法
            if ( !isNode ) {
                cache[ id ].toJSON = jQuery.noop;
            }
        }
    
        // 如果name是对象或者函数
        if ( typeof name === "object" || typeof name === "function" ) {
            // 如果是jQuery内部私用数据
            if ( pvt ) {
                // 则将数据保存在指定ID的对应缓存中
                cache[ id ] = jQuery.extend( cache[ id ], name );
            } else {
                //否则保存在指定ID对应缓存的data属性中
                cache[ id ].data = jQuery.extend( cache[ id ].data, name );
            }
        }
    
        //定位缓存中的数据
        thisCache = cache[ id ];
    
        // 区分内部私用以及公用来避免内部数据和用户定义数据的key重复导致的互相覆盖
        // 还有一个有趣的问题,为什么用户数据在data中,而内部数据直接在缓存对象里,而不是反过来呢?
        // 如果不是内部私用
        if ( !pvt ) {
            // 如果缓存中没有data属性,则初始化一个
            if ( !thisCache.data ) {
                thisCache.data = {};
            }
    
            // 定位缓存位置
            thisCache = thisCache.data;
        }
    
        // 如果data已定义,则是写入操作,写入数据
        if ( data !== undefined ) {
            thisCache[ jQuery.camelCase( name ) ] = data;
        }
    
        // 如果name是字符串,即通过字符串来获取数据
        if ( getByName ) {
    
            // 首先通过name来获取
            ret = thisCache[ name ];
    
            // 看看上面方法有没有得到数据
            if ( ret == null ) {
    
                // 如果没有,则用驼峰式name来获取
                ret = thisCache[ jQuery.camelCase( name ) ];
            }
        } else {
            // 不是则直接将数据传出
            ret = thisCache;
        }
    
        return ret;
    }

    jQuery 2.0中data的实现依然同1.9版本差不多,当然这也不一定是IE6-7的原因才将DOM节点和JS对象分开处理的,我们知道JS引擎读取DOM数据的过程是较为费时费力的,从这个角度来看,将DOM节点的缓存设计在全局会是个比较快的方案。

    这里还有两个有趣的问题:

    1. 如果传进去的data是函数,那么到底缓存了什么?
    2. 为什么用户数据在data中,而内部数据直接在缓存对象里,而不是反过来呢?

    internalRemoveData

    function internalRemoveData( elem, name, pvt /* For internal use only */ ){
        // 判断该对象能不能绑定数据
        if ( !jQuery.acceptData( elem ) ) {
            return;
        }
    
        var thisCache, i, l,
    
            isNode = elem.nodeType,
    
            cache = isNode ? jQuery.cache : elem,
            id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
    
        // 如果缓存对象根本不存在,那么就不用删除了
        if ( !cache[ id ] ) {
            return;
        }
    
        // 如果name存在
        if ( name ) {
    
            // 定位缓存位置
            thisCache = pvt ? cache[ id ] : cache[ id ].data;
    
            // 如果缓存存在
            if ( thisCache ) {
    
                // 支持以空格分隔的字符串
                if ( !jQuery.isArray( name ) ) {
    
                    // try the string as a key before any manipulation
                    // 看看字符串name存不存在
                    if ( name in thisCache ) {
                        // 定位要删除的缓存
                        name = [ name ];
                    // 不存在证明传进来的是以空格分隔的字符串或者需要转成驼峰写法
                    } else {
    
                        //转成驼峰写法
                        name = jQuery.camelCase( name );
                        //看看现在对不对
                        if ( name in thisCache ) {
                            name = [ name ];
                        //不对证明是以空格分隔的字符串
                        } else {
                            //以空格分隔字符串
                            name = name.split(" ");
                        }
                    }
                } else {
                    // 如果是数组,则预处理
                    name = name.concat( jQuery.map( name, jQuery.camelCase ) );
                }
    
                // 遍历删除
                for ( i = 0, l = name.length; i < l; i++ ) {
                    delete thisCache[ name[i] ];
                }
    
                // 如果缓存非空,则退出,证明缓存如果都空了,就要删掉它
                if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
                    return;
                }
            }
        }
    
        // 如果不是私有的
        if ( !pvt ) {
            // 删除data属性
            delete cache[ id ].data;
    
            // 如果缓存对象并非空的,证明可能还有些私有属性存储了,退出
            if ( !isEmptyDataObject( cache[ id ] ) ) {
                return;
            }
        }
    
        // 到这里已经所有缓存数据都没有了,可以清理ID之类的东西了
        // 如果是DOM节点
        if ( isNode ) {
            // 清理数据
            jQuery.cleanData( [ elem ], true );
    
        // 看看能不能用delete方法删除
        // 还要判断cache本身是不是window对象,否则会抛错
        } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
            delete cache[ id ];
    
        // 如果不能删除则设为null
        } else {
            cache[ id ] = null;
        }
    }

    jQuery.fn.data

    jQuery.fn.data = function( key, value ) {
        var attrs, name,
            elem = this[0],
            i = 0,
            data = null;
    
        // 如果key没有被定义,即要得到所有数据
        if ( key === undefined ) {
            // 如果长度不为0
            if ( this.length ) {
                // 用jQuery.data获取第一个元素的数据
                data = jQuery.data( elem );
                
                // 如果元素是节点,对应的内部数据parsedAttrs不存在
                if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
                    // 从attrubutes中获取数据
                    attrs = elem.attributes;
                    // 遍历
                    for ( ; i < attrs.length; i++ ) {
                        name = attrs[i].name;
                        
                        // 看看属性名是不是data-xxx,就是要支持HTML5 data-Attributes
                        if ( !name.indexOf( "data-" ) ) {
                            // 是则数据名为data-后面的字符串
                            name = jQuery.camelCase( name.slice(5) );
    
                            // 数据值通过daataAttr获取
                            dataAttr( elem, name, data[ name ] );
                        }
                    }
                    // 保存到对应内部缓存parsedAttrs中
                    jQuery._data( elem, "parsedAttrs", true );
                }
            }
    
            return data;
        }
    
        // 如果key是对象,则通过jQuery.data设置多个属性
        if ( typeof key === "object" ) {
            return this.each(function() {
                jQuery.data( this, key );
            });
        }
    
        // 否则用access操作链式或不用链式
        return jQuery.access( this, function( value ) {
    
            // 如果value没有定义,则是读取操作
            if ( value === undefined ) {
                // 如果有第一个元素,则返回对应的数据,否则为空
                return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
            }
    
            // 设置操作调用jQuery.data来赋值
            this.each(function() {
                jQuery.data( this, key, value );
            });
        }, null, value, arguments.length > 1, null, true );
    };

    这个方法主要是支持了HTML5 data-Attributes。

    我们可以发现,实际上jQuery最终也把data-Attributes数据也保存到缓存中,这样是为了不再DOM和JS引擎中频繁读取。

    jQuery.fn.removeData

    jQuery.fn.removeData = function( key ) {
        return this.each(function() {
            jQuery.removeData( this, key );
        });
    }

    这个就很简单法了,只是遍历所有元素使用removeData而已。

    dataAttr

    function dataAttr( elem, key, data ) {
        // 从data-*获取数据
        if ( data === undefined && elem.nodeType === 1 ) {
    
            // 预处理name,将驼峰式替换成data-*-*形式
            var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
    
            // 尝试获取该attribute的数据
            data = elem.getAttribute( name );
    
            // 如果数据是字符串
            if ( typeof data === "string" ) {
                try {
                    // 如果数据是"true",则为true
                    data = data === "true" ? true :
                    // 如果数据是"false",则为false
                    data === "false" ? false :
                    // 如果数据时"null",则为null
                    data === "null" ? null :
                    // 将字符串转成数字,再转成字符串看看有没有改变,没改变则证明是数字
                    +data + "" === data ? +data :
                    // 否则测试数据是否是以{}包裹,是则尝试转成对象
                    rbrace.test( data ) ? jQuery.parseJSON( data ) :
                        //否则就当它是普通字符串
                        data;
                } catch( e ) {}
    
                //保存数据
                jQuery.data( elem, key, data );
    
            // 否则数据未定义
            } else {
                data = undefined;
            }
        }
    
        return data;
    }

    扩展阅读

    jQuery最核心的基础设施之一——数据缓存模块进化史 . 司徒正美 . 2012-11-19

  • 相关阅读:
    struts框架的基本使用
    软件体系结构的艺术阅读笔记3
    软件架构设计阅读笔记3
    TensorFlow实现线性回归算法
    python使用pycharts调用国家地图实现数据可视化
    pip出现WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.,已解决
    tensorflow使用Session模块时报错:AttributeError: module 'tensorflow' has no attribute 'Session',已解决
    软件架构设计阅读笔记2
    软件体系结构的艺术阅读笔记2
    python快速求一个数组的最大值/最小值及其索引
  • 原文地址:https://www.cnblogs.com/justany/p/2868076.html
Copyright © 2011-2022 走看看