深入学习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 相关文章推荐
prototype1.4中文手册
Sep 22 Javascript
JS获取父节点方法
Aug 20 Javascript
Javascript document.referrer判断访客来源网址
May 15 Javascript
一个可绑定数据源的jQuery数据表格插件
Jul 17 Javascript
jquery实现图片等比例缩放以及max-width在ie中不兼容解决
Mar 21 Javascript
JS中的构造函数详细解析
Mar 10 Javascript
jquery实现超简洁的TAB选项卡效果代码
Aug 28 Javascript
第三章之Bootstrap 表格与按钮功能
Apr 25 Javascript
AngularJS 自定义指令详解及示例代码
Aug 17 Javascript
Angular PWA使用的Demo示例
Jan 31 Javascript
在layui.use 中自定义 function 的正确方法
Sep 16 Javascript
layui table 复选框跳页后再回来保持原来选中的状态示例
Oct 26 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
PHP中开发XML应用程序之基础篇 添加节点 删除节点 查询节点 查询节
2010/07/09 PHP
PHP Session 变量的使用方法详解与实例代码
2013/09/11 PHP
PHP生成指定长度随机数最简洁的方法
2014/07/14 PHP
phpcms中的评论样式修改方法
2016/10/21 PHP
jquery入门—访问DOM对象方法
2013/01/07 Javascript
jquery中对于批量deferred的处理方法
2014/01/22 Javascript
可编辑下拉框的2种实现方式
2014/06/13 Javascript
jquery学习总结(超级详细)
2014/09/04 Javascript
Js制作点击输入框时默认文字消失的效果
2015/09/05 Javascript
AngularJS 入门教程之HTML DOM实例详解
2016/07/28 Javascript
vue如何引用其他组件(css和js)
2017/04/13 Javascript
jquery中each循环的简单回滚操作
2017/05/05 jQuery
webpack构建vue项目的详细教程(配置篇)
2017/07/17 Javascript
jQuery dateRangePicker插件使用方法详解
2017/07/28 jQuery
为nuxt项目写一个面包屑cli工具实现自动生成页面与面包屑配置
2019/09/29 Javascript
Vue组件简易模拟实现购物车
2020/12/21 Vue.js
Django实现快速分页的方法实例
2017/10/22 Python
深入浅析python 中的匿名函数
2018/05/21 Python
python爬虫解决验证码的思路及示例
2019/08/01 Python
tensorflow中tf.slice和tf.gather切片函数的使用
2020/01/19 Python
Django Form设置文本框为readonly操作
2020/07/03 Python
mac安装python3后使用pip和pip3的区别说明
2020/09/01 Python
python使用selenium爬虫知乎的方法示例
2020/10/28 Python
python中的yield from语法快速学习
2020/11/06 Python
浅析HTML5的WebSocket与服务器推送事件
2016/02/19 HTML / CSS
FC-Moto瑞典:欧洲最大的摩托车服装和头盔商店之一
2018/11/27 全球购物
2019史上最全Database工程师题库
2015/12/06 面试题
后勤部长岗位职责
2013/12/14 职场文书
生产车间班组长岗位职责
2014/01/06 职场文书
销售冠军获奖感言
2014/02/03 职场文书
金融系应届毕业生求职信
2014/05/26 职场文书
纪念一二九运动演讲稿
2014/09/16 职场文书
离婚协议书怎么写的
2014/12/14 职场文书
装修公司工程部经理岗位职责
2015/04/09 职场文书
妇联2016年六一国际儿童节活动总结
2016/04/06 职场文书
2019年最新借条范本!
2019/07/08 职场文书