深入学习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 相关文章推荐
Js中sort()方法的用法
Nov 04 Javascript
javascript 获取网页参数系统
Jul 19 Javascript
基于jquery实现的一个选择中国大学的弹框 (数据、步骤、代码)
Jul 26 Javascript
用jquery写的菜单从左往右滑动出现
Apr 11 Javascript
javascript实现动态模态绑定grid过程代码
Sep 22 Javascript
JavaScript中两个字符串的匹配
Jun 08 Javascript
微信小程序 本地数据读取实例
Apr 27 Javascript
vue中component组件的props使用详解
Sep 04 Javascript
jquery应用实例分享_实现手风琴特效
Feb 01 jQuery
jQuery实现的鼠标拖动画矩形框示例【可兼容IE8】
May 17 jQuery
判断“命令按钮”是否被鼠标单击详解
Jul 31 Javascript
微信小程序实现蓝牙打印
Sep 23 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
Laravel 5.4向IoC容器中添加自定义类的方法示例
2017/08/15 PHP
yii2 开发api接口时优雅的处理全局异常的方法
2019/05/14 PHP
PHP 观察者模式深入理解与应用分析
2019/09/25 PHP
jQuery中获取checkbox选中项等操作及注意事项
2013/11/24 Javascript
Javascript 链式作用域详细介绍
2017/02/23 Javascript
JavaScript 数据类型详解
2017/03/13 Javascript
vue中如何引入jQuery和Bootstrap
2017/04/10 jQuery
微信扫码支付零云插件版实例详解
2017/04/26 Javascript
详解如何使用Node.js编写命令工具——以vue-cli为例
2017/06/29 Javascript
Vue.js基础指令实例讲解(各种数据绑定、表单渲染大总结)
2017/07/03 Javascript
Spring Boot/VUE中路由传递参数的实现代码
2018/03/02 Javascript
详解Angular5 路由传参的3种方法
2018/04/28 Javascript
webpack4 入门最简单的例子介绍
2018/09/05 Javascript
JS中使用cavas截图网页并解决跨域及模糊问题
2018/11/13 Javascript
vscode下vue项目中eslint的使用方法
2019/01/13 Javascript
JavaScript数据结构与算法之二叉树添加/删除节点操作示例
2019/03/01 Javascript
python去除所有html标签的方法
2015/05/05 Python
通过Python爬虫代理IP快速增加博客阅读量
2016/12/14 Python
Python获取某一天是星期几的方法示例
2017/01/17 Python
深入浅析Python中的yield关键字
2018/01/24 Python
python3.5安装python3-tk详解
2019/04/26 Python
在python中用url_for构造URL的方法
2019/07/25 Python
详细介绍Python进度条tqdm的使用
2019/07/31 Python
python 通过邮件控制实现远程控制电脑操作
2020/03/16 Python
如何在pycharm中安装第三方包
2020/10/27 Python
Python 实现PS滤镜中的径向模糊特效
2020/12/03 Python
PyQt5中QSpinBox计数器的实现
2021/01/18 Python
pytho matplotlib工具栏源码探析一之禁用工具栏、默认工具栏和工具栏管理器三种模式的差异
2021/02/25 Python
3个CCIE对一个工程师的面试题
2012/05/06 面试题
《雨霖铃》听课反思
2014/02/13 职场文书
协议书样本
2014/04/23 职场文书
文明城市标语
2014/06/16 职场文书
领导干部学习“三严三实”思想汇报
2014/09/15 职场文书
党员转正意见怎么写
2015/06/03 职场文书
Python List remove()实例用法详解
2021/08/02 Python
关于SpringBoot 使用 Redis 分布式锁解决并发问题
2021/11/17 Redis