深入学习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 相关文章推荐
jquery.validate使用攻略 第二部
Jul 01 Javascript
window.dialogArguments 使用说明
Apr 11 Javascript
HTML Color Picker(js拾色器效果)
Aug 27 Javascript
JavaScript 模块化编程(笔记)
Apr 08 Javascript
Node.js中常规的文件操作总结
Oct 13 Javascript
vue如何实现observer和watcher源码解析
Mar 09 Javascript
从零开始学习Node.js系列教程六:EventEmitter发送和接收事件的方法示例
Apr 13 Javascript
微信小程序 聊天室简单实现
Apr 19 Javascript
Vux+Axios拦截器增加loading的问题及实现方法
Nov 08 Javascript
帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)
Aug 23 Javascript
实例讲解React 组件
Jul 07 Javascript
Vue 3.0中jsx语法的使用
Nov 13 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
CPU步进是什么意思?i3-9100F B0步进和U0步进区别知识科普
2020/03/17 数码科技
提问的智慧(2)
2006/10/09 PHP
php 各种应用乱码问题的解决方法
2010/05/09 PHP
有关phpmailer的详细介绍及使用方法
2013/01/28 PHP
php写入数据到CSV文件的方法
2015/03/14 PHP
PHP常用文件操作函数和简单实例分析
2016/06/03 PHP
php制作圆形用户头像的实例_自定义封装类源代码
2017/09/18 PHP
用Laravel Sms实现laravel短信验证码的发送的实现
2018/11/29 PHP
php回调函数处理数组操作示例
2020/04/13 PHP
!DOCTYPE声明对JavaScript的影响分析
2010/04/12 Javascript
createElement与createDocumentFragment的点点区别小结
2011/12/19 Javascript
防止浏览器记住用户名及密码的简单实用方法
2013/04/22 Javascript
node.js实现端口转发
2016/04/14 Javascript
JavaScript知识点总结(四)之逻辑OR运算符详解
2016/05/31 Javascript
原生js图片轮播效果实现代码
2016/10/19 Javascript
纯JavaScript手写图片轮播代码
2016/10/20 Javascript
概述jQuery中的ajax方法
2016/12/16 Javascript
实现Vue的markdown文档可以在线运行的方法示例
2018/12/11 Javascript
利用原生JavaScript实现造日历轮子实例代码
2019/05/08 Javascript
vue.js中导出Excel表格的案例分析
2019/06/11 Javascript
vue.js 2.0实现简单分页效果
2019/07/29 Javascript
JS实现横向跑马灯效果代码
2020/04/20 Javascript
python中pycurl库的用法实例
2014/09/30 Python
Python __setattr__、 __getattr__、 __delattr__、__call__用法示例
2015/03/06 Python
基于python时间处理方法(详解)
2017/08/14 Python
python机器学习理论与实战(五)支持向量机
2018/01/19 Python
Python爬虫包BeautifulSoup学习实例(五)
2018/06/17 Python
基于canvas的骨骼动画的示例代码
2018/06/12 HTML / CSS
领导视察欢迎词
2014/01/15 职场文书
先进集体事迹材料
2014/02/17 职场文书
工程建设实施方案
2014/03/14 职场文书
大学生就业意向书范文
2014/04/01 职场文书
市场部经理岗位职责
2015/02/02 职场文书
Python爬虫基础之初次使用scrapy爬虫实例
2021/06/26 Python
nginx配置虚拟主机的详细步骤
2021/07/21 Servers
Python如何将list中的string转换为int
2022/07/15 Ruby