jQuery 源码系列(八)data 缓存机制

欢迎来我的专栏检察系列文章。

不盘算引见 deferred,或许放到背面今后去引见,因为我关于 js 的异步存在着恐惊,看了半天代码,发明,用挺好用的,一看源码,全傻眼了。假如你感兴趣,这边链接1链接2

《jQuery 源码系列(八)data 缓存机制》

数据缓存

jQuery 最初以便利 DOM 操纵而盛行,而 DOM 的本质实在就是对象,开发者们又习惯性的将一些标志直接扔给 DOM 本领,这会带来内存走漏的题目。

比方关于斐波那契数列,要领能够有递归,有迭代,假如用 js 来写的话有一个比较有意义的要领,就是用缓存来完成:

function fib(n){
  if(n == 1 || n == 0)
    return n;
  if(!fib[n-1]){
    fib[n-1] = fib(n-1);
  }
  if(!fib[n-2]){
    fib[n-2] = fib(n-2);
  }
  return fib[n-1] + fib[n-2];
}

因为 fib 不仅是函数,而且是对象,JS 中万物都是对象,所以才有了这类缓存的解决办法,这就是前面所说的,不过是在 DOM 上完成的。

固然这类要领也会有弊病,形成内存走漏。当代的浏览器有自动接纳内存的机制,但当涌现轮回援用或闭包的时刻,就会发生内存走漏题目

就不深切去议论了。

jQuery 的缓存机制

来看看 jQuery 中进步的数据缓存机制,有两个函数,离别是 jQuery.data()jQuery.fn.data(),能够看出来,一个是在 jQuery 对象上,一个是在 jQuery 天生的对象上。假如仔细阅读的话,你会发明 jQuery 中许多函数都有两个,原型上一个,jQuery 上一个。

jQuery.data() 有两种运用,一个用于绑定,一个用于查询:

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

  2. jQuery.data( element, key )

上面的 element 参数示意 DOM 元素,比方一个例子以下:

jQuery.data(document.body, 'foo', 52);
jQuery.data(document.body, 'bar', 'test');
jQuery.data(document.body, 'foo'); // 52
jQuery.data(document.body, 'bar'); // "test"

另有 .data() 要领,.data(),这个函数就直接在 jquery 对象上执行绑定 data:

$("body").data("foo", 52);
$("body").data("bar", { myType: "test", count: 40 });
$("body").data({ baz: [ 1, 2, 3 ] });
 
$("body").data("foo"); // 52
$("body").data(); // { foo: 52, bar: { myType: "test", count: 40 }, baz: [ 1, 2, 3 ] }

这边有一个小细节数据缓存接口

var jq1 = $("body");
var jq2 = $("body");

jq1.data('a', 1);
jq2.data('a', 2);

jq1.data('a'); //2
jq2.data('a'); //2
// 数据被掩盖

$.data(jq1, 'b', 3);
$.data(jq2, 'b', 4);

$.data(jq1, 'b'); //3
$.data(jq2, 'b'); //4
// 不会被掩盖

能够看出来,经由过程这两种要领绑定的数据,实际上是不一样的,前者会被掩盖,而后者不会,申明在 cache 中肯定有某种神奇的气力将他们区分开来

源码

在 jQuery 中的源码,大抵是如许的组织:

function Data(){...}
Data.prototype = {
  cache: function(){...},
  set: function(){...},
  get: function(){...},
  access: function(){...},
  remove: function(){...},
  hasData: function(){...}
}

var dataUser = new Data();

jQuery.extend({
  data: function( elem, name, data ) {
    return dataUser.access( elem, name, data );
  },
  hasData: function( elem ) {
    return dataUser.hasData( elem ) || dataPriv.hasData( elem );
  },
  removeData: function( elem, name ) {
    dataUser.remove( elem, name );
  }
})

jQuery.fn.extend({
  data: function(){
    ...
    dataUser...
    ...
  },
  removeData: function(){...}
})

因为之前已弄懂 jQuery 内部组织,关于这个一点也不惊奇,在 jQuery 和 jQuery 的原型上离别有一个 data 函数,用来处置惩罚各自的状况。

既然已知道了 data 的基础组织,我们来各个击破,先来看一下 function Data()

function Data() {
  // jQuery.expando 是 jQuery 的标识
  this.expando = jQuery.expando + Data.uid++;
}

Data.uid = 1;

jQuery.expando = ('3.1.1' + Math.random()).replace( /\D/g, "" )
// "3.1.10.9610206515567563".replace( /\D/g, "" )
// "31109610206515567563"

接着是 prototype

Data.prototype = {
  // 竖立一个 cache
  cache: function( owner ) {
    // Check if the owner object already has a cache
    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 ) ) {

        // 推断 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
        } 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 );

    if ( typeof data === "string" ) {
      cache[ jQuery.camelCase( data ) ] = value;

    // 处置惩罚 data 为这类状况: [ owner, { properties } ]
    } else {
      // Copy the properties one-by-one to the cache object
      for ( prop in data ) {
        cache[ jQuery.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 ][ jQuery.camelCase( key ) ];
  },
  // 用来接见,将 get、set 结合到一同,并对 underfined 推断
  access: function( owner, key, value ) {
    if ( key === undefined ||
        ( ( key && typeof key === "string" ) && value === undefined ) ) {

      return this.get( owner, key );
    }
    this.set( owner, key, value );
    return value !== undefined ? value : key;
  },
  // 用于移除 cache
  remove: function( owner, key ) {
    var i,
      cache = owner[ this.expando ];

    if ( cache === undefined ) {
      return;
    }

    if ( key !== undefined ) {

      // 支撑删除数组花样的 key
      if ( jQuery.isArray( key ) ) {
        key = key.map( jQuery.camelCase );
      } else {
        key = jQuery.camelCase( key );
        // 为了保持一致,强行的组织了一个 数组
        key = key in cache ?
          [ key ] :
          ( key.match( rnothtmlwhite ) || [] );
      }
      i = key.length;
      // 删
      while ( i-- ) {
        delete cache[ key[ i ] ];
      }
    }
    // cache 为空的时刻,删除悉数缓存
    if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
      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 );
  }
};

然后是 jQuery.data()

var dataPriv = new Data(); //今后会讲到
var dataUser = new Data();

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 );
  }
} );

源码内里有 dataPriv 和 dataUser,作者做了一个 TODO 标记,

接着是 jQuery.fn.data()

jQuery.fn.extend( {
  data: function( key, value ) {
    var i, name, data,
      // 将第一个 dom 赋给 elem
      elem = this[ 0 ],
      attrs = elem && elem.attributes;

    // key 为 underfined,示意参数空,猎取悉数
    if ( key === undefined ) {
      if ( this.length ) {
        data = dataUser.get( elem );

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

            // 这内里从 dom 的 attribute 中搜刮 data- 开通的属性
            if ( attrs[ i ] ) {
              name = attrs[ i ].name;
              if ( name.indexOf( "data-" ) === 0 ) {
                name = jQuery.camelCase( name.slice( 5 ) );
                dataAttr( elem, name, data[ name ] );
              }
            }
          }
          dataPriv.set( elem, "hasDataAttrs", true );
        }
      }

      return data;
    }

    // object 范例
    if ( typeof key === "object" ) {
      return this.each( function() {
        dataUser.set( this, key );
      } );
    }
    // key value 的状况,应用 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 );
  },

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

data 函数略有不同,但思绪也很清晰。

有几个要提一下的函数

个中,有几个函数,也来引见一下,acceptData

var acceptData = function( owner ) {
  // Accepts only:
  //  - Node
  //    - Node.ELEMENT_NODE
  //    - Node.DOCUMENT_NODE
  //  - Object
  //    - Any
  return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
};

acceptData 是推断 owner 的范例,详细关于 nodeType,去看看这里吧。

jQuery.camelCase

jQuery.camelCase = function (string) {
  return string.replace(/^-ms-/, "ms-").replace(/-([a-z])/g, function (all, letter) {
    return letter.toUpperCase();
  });
}

这个函数就是做了一些特别字符串的 replace,详细有啥用,我也不是很清晰。

isEmptyObject 是推断一个 Object 是不是为空的函数,挺有意义的,能够自创:

jQuery.isEmptyObject = function (obj) {
    var name;
    for (name in obj) {
        return false;
    }
    return true;
}

dataAttr 是一个从 DOM 中搜刮以 data- 开首属性的函数:

function dataAttr( elem, key, data ) {
  var name;
  // If nothing was found internally, try to fetch any
  // data from the HTML5 data-* attribute
  if ( data === undefined && elem.nodeType === 1 ) {
    name = "data-" + key.replace( /[A-Z]/g, "-$&" ).toLowerCase();
    // 应用 dom 本身的 get 操纵
    data = elem.getAttribute( name );

    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 );
    } else {
      data = undefined;
    }
  }
  return data;
}

总结

jQuery 的 data 缓存从源码来看的话,真的不是很难,而且不难发明,jQuery 缓存的本质,实在就是在内部先弄一个 Object,然后和缓存体(DOM)竖立一对一的联络,一切增编削查的操纵,都是围绕着 jQuery 内部来的,不直接对 DOM 操纵,如许就能够防止内存走漏。而且从源码来看,jQuery 的缓存机制自带清内存操纵,更是如虎添翼呀。

参考

jQuery 2.0.3 源码剖析 数据缓存

jQuery.data()

本文在 github 上的源码地点,欢迎来 star。

欢迎来我的博客交换。

    原文作者:songjz
    原文地址: https://segmentfault.com/a/1190000008433645
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞