zoukankan      html  css  js  c++  java
  • 深入jQuery中的data()

    引入

      data函数在jQuery中看起来很不起眼, 就像沙滩上一颗平凡的沙子, 但仔细一瞅, 却惊讶的发现data是jQuery中无比重要的一环, 甚至jQuery中各种事件都基于此。

    data有什么作用?

      在我们平时js编码过程中,我们经常会向DOM元素中添加各种自定义属性,这样有一个弊端。

      1  假设我们在DOM元素中添加了一个属性,这个属性指向了某个js对象。 dom1.ele = jsObj

      2  当这个js对象发挥完作用后,我们已经用不到他了。这时候按理说应该把这个js变量清空,释放内存。大家都知道,如果一个js对象不存在任何外在引用的话,解释器会自动将其在内存中删除,这也是javascript相对于c++等手动管理内存的程序的优点。

      3  但是这时候问题来了,因为DOM元素引用了这个js对象,尽管这个js对象已经没有存在的意义了,但是解释器是不会把他删除的。如果想要把其删除,我们可能需要将DOM元素的这个属性设置为null。

      4  我们编写了这么多的代码,哪里能把 每个js对象是不是被DOM元素引用了都记住啊?

      5  而且,假如DOM元素与js对象之间相互循环引用,根本就无法删除! 这就是内存泄漏

      6  所以,为了避免这种情况的发生,我们要尽量避免 引用数据(这里的引用数据可以说是javascript对象) 直接依附在DOM对象上。

      7  data就是用来搞定以上问题的方法。

    data是如何搞定以上问题的?

    首先来说一说jQuery中Data实现的大体思路:

      1  首先我们创建一个数据缓存池,这个缓存池专门用来存储  向 DOM对象或者jQuery对象附加的额外数据。

      2  当我们要向DOM对象或者jQuery对象附加额外数据的时候,我们附加的数据其实是保存于这个缓存池中

      3  DOM对象或者jQuery对象生成一个额外属性,这个属性保存了 附加数据在缓存池中的‘门牌号’(位置或者索引)

      4  当我们访问DOM对象或者jQuery对象的附加数据时,实际上是先取得其附加数据的门牌号,然后找到缓存池中对应门牌号的数据,进行操作。

    大体思路讲完,那么来分析一下具体思路:

    在jQuery中,有一个Data构造函数,每当运行这个构造函数时,就会生成一个实例。jQuery默认会自动生成两个Data实例:

      var dataPriv = new Data()   jQuery私有的,我们尽量不要对这个实例进行操作。

      var dataUser = new Data()   这个就是服务于用户了,我们使用data()方法都是对这个实例进行操作。

    所有的Data实例都有以下属性:

      expando:  值为字符串类型,每个Data实例的expando属性的值都不相同,用来区分不同的Data实例,类似于id的作用,expando的值就是上文中的额外属性

      uid:   这就是上文中的门牌号,初始为1,随着不同对象的附加数据的加入,自增长。

      cache : 一个对象 {} ,这就是缓存池了。

    来个实例:

    $(document.body).data('aaa', 'value-aaa')
    console.dir(document.body)

    body对象有一个名为jquer210023......的额外属性

      这个属性的名称就是dataUser的expando的值

      这个属性的值就是门牌号

    总结: data实际上就是对js对象或者DOM对象的额外属性做了一个集中的管理。对于那些不会产生内存泄漏的额外数据,我们也可以直接向js对象或者DOM对象附加。

    好,理清楚上面的关系后,我们再来看一下源码:

    define([
        "../core",
        "../var/rnotwhite",
        "./accepts"
    ], function( jQuery, rnotwhite ) {
    
    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, {
            get: function() {
                return {};
            }
        });
        //  jQuery.expando = "jQuery" + ( version + Math.random() ).replace( /D/g, "" ) expando是一个jQuery的唯一标示
        // 格式是:'jQuery\d*'  也就是'jQuery'+ 多个数字。这里为啥要搞得这么麻烦呢?
        // 应因为我们可能会创建多个Data对象,为了保证每个Data对象的expando属性的值不相等,所以这么搞
        this.expando = jQuery.expando + Math.random();
    }
    
    Data.uid = 1;  // Data函数的属性,'静态属性'
    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.
            // 若owner在该缓存池中存在对应的缓存对象,则返回混存对象的key(是一个数字),
            // 若owner在该缓存池中不存在对应的缓存对象,则在缓存池中为其创建一个缓存对象,并返回该缓存对象的key
            if ( !Data.accepts( owner ) ) {
                return 0;
            }
    
            var descriptor = {},
                // Check if the owner object already has a cache key
                // 检查owner对象在该缓存池中是否存在缓存
                unlock = owner[ this.expando ];  // 是一个数字,用来作为缓存池中缓存对象的key
    
            // If not, create one
            // 如果没有,则创建一个
            if ( !unlock ) {
                unlock = Data.uid++;
    
                // Secure it in a non-enumerable, non-writable property
                // 给owner附加一个属性  owner[this.expando] = unlock ,并且该属性不能被枚举,
                try {
                    descriptor[ this.expando ] = { value: unlock };
                    Object.defineProperties( owner, descriptor );
    
                // Support: Android<4
                // Fallback to a less secure definition
                } catch ( e ) {
                    descriptor[ this.expando ] = unlock;
                    jQuery.extend( owner, descriptor );
                }
            }
    
            // Ensure the cache object
            // 确保owner对应的缓存对象已存在
            if ( !this.cache[ unlock ] ) {
                this.cache[ unlock ] = {};
            }
            //  返回unlock
            return unlock;
        },
        set: function( owner, data, value ) {
            // 设置owner对应的缓存对象
            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 ),  // 获取owner的对应的缓存对象在缓存池中的key(这里的key,是键值对中的键的意思)
                cache = this.cache[ unlock ];  // 获取owner所对应的缓存对象
    
            // 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 ) {
            // 获取owner对象的名为key的属性值
            // owner:是一个对象(可以是jQuery对象也可以是DOM对象)   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 ) ]; //  owner的缓存对象
    
            return key === undefined ? cache : cache[ key ];  // 没指定key的话就返回整个缓存对象,若指定了key则返回在该缓存对象的key属性的值
        },
        access: function( owner, key, value ) {
            var stored;
            // In cases where either:
            //
            //   1. No key was specified   没有指定key
            //   2. A string key was specified, but no value provided  指定了字符串格式的key,但没有指定value
            //
            // 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或者指定了字符串格式的key,但没有指定value
                    ((key && typeof key === "string") && value === undefined) ) {
    
                // 没有指定key:获取整个缓存对象
                // 指定了字符串格式的key,但没有指定value: 获取缓存对象中key的值
                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:
            // 当key不是一个字符串,或者key和value都指定了,就会根据情况进行设置或者扩展
            //
            //   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 ) {
            // 清空owner对应的缓存对象,或者移除缓存对象中的某个键值对
            var i, name, camel,
                unlock = this.key( owner ),
                cache = this.cache[ unlock ];
            // 如果没有指定key,则清空缓存对象
            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 ) {
            // 检查owner在该缓存池中是否存在缓存对象
            return !jQuery.isEmptyObject(
                this.cache[ owner[ this.expando ] ] || {}
            );
        },
        discard: function( owner ) {
            if ( owner[ this.expando ] ) {
                delete this.cache[ owner[ this.expando ] ];
            }
        }
    };
    
    return Data;
    });
    Data构造函数源码解析

    可能会有同学问道:如果我想对dataPriv进行操作该如何?

    请看源码:

    jQuery.extend({
        hasData: function( elem ) {
            return dataUser.hasData( elem ) || dataPriv.hasData( elem );
        },
    
        data: function( elem, name, data ) {
            return dataUser.access( elem, name, data );
        },
    
        removeData: function( elem, name ) {
            dataUser.remove( elem, name );
        },
    
        // TODO: Now that all calls to _data and _removeData have been replaced
        // with direct calls to dataPriv methods, these can be deprecated.
        _data: function( elem, name, data ) {
            return dataPriv.access( elem, name, data );
        },
    
        _removeData: function( elem, name ) {
            dataPriv.remove( elem, name );
        }
    });

    通过源码,我们可以看出:

      jQuery.data() jQuery.remove()都是对dataUser进行操作,而jQuery._data() jQuery._remove()都是对dataPriv进行操作。

    理解jQuery.data(ele,name,data) 与 jQuery().data(key,value)的不同。

      通过上面的源码,我们可以看到jQuery.data(ele,name,data)是对ele元素附加数据。

      而jQuery().data(key,value)则会为jQuery对象中的所有DOM对象分别附加数据

    来看源码(删减了部分):

        jQuery.fn.extend({
        data: function( key, value ) {
            var i, name, data,
                elem = this[ 0 ],
                attrs = elem && elem.attributes;return access( this, function( value ) {
                var data,
                    camelKey = jQuery.camelCase( key );

    // 从这里可以看出,为jQuery对象中的每个DOM元素分别附加数据 this.each(function() { // First, attempt to store a copy or reference of any // data that might've been store with a camelCased key. var data = dataUser.get( this, camelKey ); // For HTML5 data-* attribute interop, we have to // store property names with dashes in a camelCase form. // This might not apply to all properties...* dataUser.set( this, camelKey, value ); // *... In the case of properties that might _actually_ // have dashes, we need to also store a copy of that // unchanged property. if ( key.indexOf("-") !== -1 && data !== undefined ) { dataUser.set( this, key, value ); } }); }, null, value, arguments.length > 1, null, true ); }, removeData: function( key ) { return this.each(function() { dataUser.remove( this, key ); }); } });

     -----------------------------------------------分隔线---------------------------------------------------

    上文中的所有源码:为jQuery.1.12  

  • 相关阅读:
    如何评价ionic和react native?
    ionic 之 基本布局
    TensorFlow中文社区---下载与安装
    深入代码详谈irqbalance【转】
    用Gen4消除电容触摸屏设计屏障【转】
    如何解决触摸屏的电磁干扰问题【转】
    Linux firmware 加载【转】
    Linux Shell 文本处理工具集锦【转】
    刷leetcode是什么样的体验?【转】
    知乎上的一些文章---leetcode【笔记1】
  • 原文地址:https://www.cnblogs.com/MnCu8261/p/6105103.html
Copyright © 2011-2022 走看看