欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深入学习jQuery中的data()

程序员文章站 2022-10-19 10:49:13
data有什么作用? 在我们平时js编码过程中,我们经常会向dom元素中添加各种自定义属性,这样有一个弊端。   1、假设我们在dom元素中添加了一个属性,这个属性指向...

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

可能会有同学问道:如果我想对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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者动作能带来一定的帮助,如果有疑问大家可以留言交流。