深入学习jQuery中的data()


Posted in Javascript onDecember 22, 2016

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

总结

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

Javascript 相关文章推荐
javascript Array.remove() 数组删除
Aug 06 Javascript
JQuery中的$.getJSON 使用说明
Mar 10 Javascript
jQuery学习笔记 操作jQuery对象 属性处理
Sep 19 Javascript
jQuery实现form表单reset按钮重置清空表单功能
Dec 18 Javascript
将HTML格式的String转化为HTMLElement的实现方法
Aug 07 Javascript
JS+CSS实现表格高亮的方法
Aug 05 Javascript
jQuery简单实现iframe的高度根据页面内容自适应的方法
Aug 01 Javascript
jquery实现瀑布流效果 jquery下拉加载新数据
Dec 12 Javascript
JS获取填报扩展单元格控件的值的解决办法
Jul 14 Javascript
ztree实现左边动态生成树右边为内容详情功能
Nov 03 Javascript
vue中实现移动端的scroll滚动方法
Mar 03 Javascript
js面试题之异步问题的深入理解
Sep 20 Javascript
读Javascript高性能编程重点笔记
Dec 21 #Javascript
解决拦截器对ajax请求的拦截实例详解
Dec 21 #Javascript
原生的强大DOM选择器querySelector介绍
Dec 21 #Javascript
懒加载实现的分页&amp;&amp;网站footer自适应
Dec 21 #Javascript
JS树形菜单组件Bootstrap TreeView使用方法详解
Dec 21 #Javascript
Vue.js 递归组件实现树形菜单(实例分享)
Dec 21 #Javascript
详解jQuery选择器
Dec 21 #Javascript
You might like
基于pear auth实现登录验证
2010/02/26 PHP
PHP操作XML作为数据库的类
2010/12/19 PHP
各种效果的jquery ui(接口)介绍
2008/09/17 Javascript
JS 强制设为首页的代码
2009/01/31 Javascript
toString()一个会自动调用的方法
2010/02/08 Javascript
javascript提取URL的搜索字符串中的参数(自定义函数实现)
2013/01/22 Javascript
js计算字符串长度包含的中文是utf8格式
2013/10/15 Javascript
js数字转换为float,取N位小数
2014/02/08 Javascript
JQuery包裹DOM节点的方法
2015/06/11 Javascript
Vue.js实现拖放效果的实例
2016/09/30 Javascript
利用jQuery对无序列表排序的简单方法
2016/10/16 Javascript
如何防止INPUT按回车自动提交表单FORM
2016/12/06 Javascript
Jquery实现跨域异步上传文件总结
2017/02/03 Javascript
Vue项目安装插件并保存
2019/01/28 Javascript
JS实现使用POST方式发送请求
2019/08/30 Javascript
vue elementui 实现搜索栏公共组件封装的实例代码
2020/01/20 Javascript
基于JS实现视频上传显示进度条
2020/05/12 Javascript
通过python+selenium3实现浏览器刷简书文章阅读量
2017/12/26 Python
Python3 修改默认环境的方法
2019/02/16 Python
python并发编程多进程 模拟抢票实现过程
2019/08/20 Python
如何基于python实现画不同品种的樱花树
2020/01/03 Python
Python中sorted()排序与字母大小写的问题
2020/01/14 Python
Python爬虫爬取百度搜索内容代码实例
2020/06/05 Python
HTML5 MiranaVideo播放器 (代码开源)
2010/06/11 HTML / CSS
美国领先的家居装饰和礼品商店:Kirkland’s
2017/01/30 全球购物
城野医生官方海外旗舰店:风靡亚洲毛孔收敛水
2018/04/26 全球购物
Revolution Beauty美国官网:英国知名化妆品网站
2018/07/23 全球购物
出纳员岗位责任制
2014/02/11 职场文书
五一劳动节演讲稿
2014/09/12 职场文书
小学教师学习党的群众路线教育实践活动心得体会
2014/10/31 职场文书
2015年“7.11”世界人口日宣传活动方案
2015/05/06 职场文书
地道战观后感400字
2015/06/04 职场文书
nginx 反向代理之 proxy_pass的实现
2021/03/31 Servers
Mysql数据库索引面试题(程序员基础技能)
2021/05/31 MySQL
SQL实现LeetCode(177.第N高薪水)
2021/08/04 MySQL
Python人工智能之混合高斯模型运动目标检测详解分析
2021/11/07 Python