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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM