浅析jQuery 3.0中的Data


Posted in Javascript onJune 14, 2016

jQuery 3.0 在6月9日正式发布了,3.0 也被称为下一代的 jQuery。这个版本从14年10月开始,其中发布过一次beta 版(2016/1/14,)和候选版(2016/05/20)。一路走来,颇为不易。

一、Data浅析

jQuery 3.0 中的 Data 是内部使用的,定义为一个“类”。一共用它创建了两个对象,dataPriv 和 dataUser。Data 有 1 个对象属性(expando)和类属性(uid),有 6 个方法,如下

浅析jQuery 3.0中的Data

下面分别解读

1、Data.uid

这是一个从 1 开始用来自增的数字。

2、expando

由 jQuery.expando 和 uid 组合而成,它用来作为元素(如DOM元素)的key,是唯一的。jQuery.expando 的生成如下

jQuery.expando = "jQuery" + ( version + Math.random() ).replace( /\D/g, "" )

即 'jQuery' + (版本号 + 随机数),然后把非数字的都去掉,比如

"jQuery" + ".." + . == "jQuery..."

去掉非数字变为

jQuery30009423638425146147"

jQuery 3.0 内部变量 dataPriv 和 dataUser 生成 expando 如下

jQuery 300 024727210109188635 1
jQuery 300 024727210109188635 2

第三部分是随机数,每次刷新都会变,其它部分的不变。

3、cache

cache 方法会给 owner 上绑定一个对象作为存储,owner 必须满足 acceptData 的,cache 会以 this.expando 为线索 key。
owner 有两种,一中是DOM元素(nodeType为1和9),另一种则是普通的JS对象。诸如 文本节点(nodeType=3)、注释节点(nodeType=8) 一律不添加。

acceptData 的定义如下

var acceptData = function( owner ) {
  // Accepts only:
  // - Node
  //  - Node.ELEMENT_NODE
  //  - Node.DOCUMENT_NODE
  // - Object
  //  - Any
  /* jshint -W018 */
  return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
};

acceptData 在 3.0 中一共有 3 处使用,分别为

Data 类的 cache 方法,为私有方法不提供给程序员使用。$.cleanData 方法,清空元素关联的所有缓存数据。为公开方法,但很少使用。该方法被用在 empty、html、replaceWith、remove 方法中。$().trigger 方法,主动派发事件,为公开方法。

如果是 DOM 元素,则直接使用点操作符赋值,如果是普通 JS 对象则使用 ES5 的 Object.defineProperty 方法,这也是 jQuery 3.0 会使用新 API 的体现。

// If it is a node unlikely to be stringify-ed or looped over
// use plain assignment
if ( owner.nodeType ) {
  owner[ this.expando ] = value;
// Otherwise secure it in a non-enumerable property
// configurable must be true to allow the property to be
// deleted when data is removed
} else {
  Object.defineProperty( owner, this.expando, {
    value: value,
    configurable: true
  } );
}

转换成如下代码

elem['jQuery3000247272101091886351'] = dataObj;
var person = {name: 'John', age: 30};
Object.defineProperty( person, 'jQuery3000247272101091886351', {
  value: dataObj,
  configurable: true
} );

cache 方法就是这样,传入 owner,只有第一次会 set ,返回 value (一个空对象),之后取到 value 后直接返回。

源码

cache: function( owner ) {
  // Check if the owner object already has a cache
  var value = owner[ this.expando ];
  // If not, create one
  if ( !value ) {
    value = {};
    // We can accept data for non-element nodes in modern browsers,
    // but we should not, see #8335.
    // Always return an empty object.
    if ( acceptData( owner ) ) {
      // If it is a node unlikely to be stringify-ed or looped over
      // use plain assignment
      if ( owner.nodeType ) {
        owner[ this.expando ] = value;
      // Otherwise secure it in a non-enumerable property
      // configurable must be true to allow the property to be
      // deleted when data is removed
      } else {
        Object.defineProperty( owner, this.expando, {
          value: value,
          configurable: true
        } );
      }
    }
  }
  return value;
},

4、set

上面的 cache 方法为 owner 建立一个以 expando 为 key 的空对象,后面所有的方法都围绕这个空对象来展开,这个空对象就被称为缓存对象,后面所有的数据都添加到它上面。set 就是给这个对象来添砖加瓦,set 每次都是先取回 cache ,再给其添加新的属性及数据。如果 data 是字符串,则以它为 key 添加,如果是对象,则遍历它添加。只需注意一点,横线连接符内部会被转成驼峰格式,这也是为了对 H5 data-xxx 的兼容 。

源码

set: function( owner, data, value ) {
  var prop,
    cache = this.cache( owner );
  // Handle: [ owner, key, value ] args
  // Always use camelCase key (gh-2257)
  if ( typeof data === "string" ) {
    cache[ jQuery.camelCase( data ) ] = value;
  // Handle: [ owner, { properties } ] args
  } else {
    // Copy the properties one-by-one to the cache object
    for ( prop in data ) {
      cache[ jQuery.camelCase( prop ) ] = data[ prop ];
    }
  }
  return cache;
},

5、get

get 简单至极,传 key 则从 cache 上取回该 key 的值,无则取回整个 cache。

源码

get: function( owner, key ) {
  return key === undefined ?
    this.cache( owner ) :
    // Always use camelCase key (gh-2257)
    owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];
},

6、access

这个方法即时 getter,也是 setter,如此而已。

getter 条件

key 是 undefined,这时取整个 cachekey 是字符串且value 是undefined,这是取指定 key 的值

setter 条件

owner、key、value 这三个参数都传

源码

access: function( owner, key, value ) {
  // In cases where either:
  //
  //  1. No key was specified
  //  2. A string key was specified, but no value provided
  //
  // 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 && typeof key === "string" ) && value === undefined ) ) {
    return this.get( owner, key );
  }
  // When the key is not a string, or both a key and value
  // are specified, set or extend (existing objects) with either:
  //
  //  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;
},

7、remove

清空绑定元素(owner)上面的缓存对象,依然需要先通过 this.expando 拿到 cache,如果传了 key 则删除指定key的值(key自身也被删除)。
当然 jQuery API 保持已有的方便性,key 可以为一个数组,这样可以批量删除多个 key。如果 key 没传则将整个 cache 删除,这里区分了 DOM 和普通 JS 对象,DOM 对象使用undefined赋值,JS 对象则使用 delete。

源码

remove: function( owner, key ) {
  var i,
    cache = owner[ this.expando ];
  if ( cache === undefined ) {
    return;
  }
  if ( key !== undefined ) {
    // Support array or space separated string of keys
    if ( jQuery.isArray( key ) ) {
      // If key is an array of keys...
      // We always set camelCase keys, so remove that.
      key = key.map( jQuery.camelCase );
    } else {
      key = jQuery.camelCase( key );
      // If a key with the spaces exists, use it.
      // Otherwise, create an array by matching non-whitespace
      key = key in cache ?
        [ key ] :
        ( key.match( rnotwhite ) || [] );
    }
    i = key.length;
    while ( i-- ) {
      delete cache[ key[ i ] ];
    }
  }
  // Remove the expando if there's no more data
  if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
    // Support: Chrome <=35 - 45
    // Webkit & Blink performance suffers when deleting properties
    // from DOM nodes, so set to undefined instead
    // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
    if ( owner.nodeType ) {
      owner[ this.expando ] = undefined;
    } else {
      delete owner[ this.expando ];
    }
  }
}, 

8、hasData

用来判断 owner 上是否有缓存数据,返回 true 或 false。

源码

hasData: function( owner ) {
  var cache = owner[ this.expando ];
  return cache !== undefined && !jQuery.isEmptyObject( cache );
}

二、Data在jQuery内部的使用

以上解读完了 Data 的所有方法,上面也提到 Data 类是在 jQuery 内部使用的,一共创建了它的两个对象:dataPriv 和 dataUser。

这两个对象在 3.0.0 中有着明确的分工,dataPriv 可以猜测到是 “data” 和 “private” 两个单词的组合后简写。即 dataPriv 是私有的用来服务 jQuery 内部方法,dataUser 用来服务那些公开给用户使用的方法。

下面看下这两个对象分布在哪些模块中使用。

浅析jQuery 3.0中的Data

完整版点击展开可查看

dataPriv
  公共
    $.hasData
    $.cleanData
    cloneCopyEvent
  队列
    $().queue
    $()._queueHooks
    $().promise
  动画
    $().animate
    $().stop
    $().finish
    showHide
  事件
    $.event.add
    $.event.remove
    $.event.dispatch
    $.event.trigger
  其它
    setGlobalEval
    domManip
    defaultPrefilter
    $().toggleClass
dataUser
  公共
    $.hasData
    $.cleanData
    cloneCopyEvent
  数据缓存
    $.data
    $.removeData
    $().data
    $().removeData
  其它
    dataAttr

以上可以看到,除了“公共”,DataPriv 用在了 jQuery 的 队列、动画、事件等模块;dataUser 用在了数据缓存及dataAttr模块。

“公共” 是指这三个方法内都用到了 dataPriv 和 dataUser

$.hasData(elem)

用来判断 elem 上是否绑定了相关的数据缓存,返回 true 和false,只有 dataPriv 和 dataUser 上都没有才返回 false

源码

hasData: function( elem ) {
return dataUser.hasData( elem ) || dataPriv.hasData( elem );
},

$.cleanData(elems)

清空 elem 上绑定的所有数据缓存,理所当然的需要同时清空 dataPriv 和 dataUser 上的。
注意:虽然这个方法在公开暴露在了 $ 上, 但官网API上却没有该方法的介绍。另使用不当会造成严重后果,比如绑定了事件后(.on),调用该方法,绑定的事件将全部失效。因为会清空 dataPriv 内的所有数据。

cloneCopyEvent(src, dest)

这是一个内部方法,$.clone 会使用到它。克隆元素时除了会克隆node节点外,绑定在node上的数据也会被克隆过去。比如

var cloneNode = $.clone(elem);

把 elem 克隆给 cloneNode,此时 elem 上添加的事件 cloneNode 上也会有。

三、1.x.x 和 2.x.x 的比较

jQuery 1.x 系列 和 2.x 系列的版本对 数据缓存模块的实现差异还是很大的。大家可以对比我11年的这篇文章

1. 缓存的数据结构

1.x (直到1.11.2) 缓存都是存储在 jQuery.cache 上的,2.x(包括3.x) 则使用了一个内部类 Data 做缓存,其主要用到了两个对象 dataPriv 和 dataUser。很明显 2.x 做的更好,它所有的缓存数据都是私有的,不会存在被误写的风险,而 1.x 的 jQuery.cache 是公开的,如果被误写(比如某个同学想当然的给$上添加一个cache对象)后果不堪设想。

2. jQuery._data

看到这个下划线就知道是私有的(约定式),在 1.x 中是仅在内部使用的,不提供给开发者。以 1.11.2 示例、这个方法被事件模块、队列模块、动画模块、setGlobalEval、cloneCopyEvent、fixCloneNodeIssues、domManip、showHide、defaultPrefilter、toggleClass 使用。3.x 则使用 dataPriv 和 dataUser 替代,大家可以对比看看。

(2/3).x 相比 1.x 明显更优,dataPriv 和 dataUser 是真正的私有的(封装的更好,更安全),比起 1.x 约定式的私有 jQuery._data。虽然 3.0.0 还保守的兼容了 jQuery._data,相信过不了多久的后续版本就会剔除。

浅析jQuery 3.0中的Data

3. 重构

1.x 以 $._data 为中心,以它来辅助实现其它 API, (2/3).x 以 dataPriv/dataUser 为中心来实现。(2/3).x 将代码重构后提取出了 Data 类,更加清晰。

以上所述是小编给大家介绍的jQuery 3.0中的Data的全部叙述,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript 联动的无限级封装类,数据采用非Ajax方式,随意添加联动
Jun 29 Javascript
JavaScript通过function定义对象并给对象添加toString()方法实例分析
Mar 23 Javascript
基于javascript实现图片切换效果
Apr 17 Javascript
使用jQuery Mobile框架开发移动端Web App的入门教程
May 17 Javascript
Vuejs第十篇之vuejs父子组件通信
Sep 06 Javascript
总结js中的一些兼容性易错的问题
Dec 18 Javascript
Vue.js图片预览插件使用详解
Aug 27 Javascript
微信小程序蓝牙连接小票打印机实例代码详解
Jun 03 Javascript
小程序如何获取多个formId实现详解
Sep 20 Javascript
浅谈vuex中store的命名空间
Nov 08 Javascript
javascript 原型与原型链的理解及实例分析
Nov 23 Javascript
JS实现导航栏楼层特效
Jan 01 Javascript
js控件Kindeditor实现图片自动上传功能
Jul 20 #Javascript
Node.js的Koa框架上手及MySQL操作指南
Jun 13 #Javascript
jQuery中的一些常见方法小结(推荐)
Jun 13 #Javascript
jQuery实现手机自定义弹出输入框
Jun 13 #Javascript
实例讲解JavaScript中的this指向错误解决方法
Jun 13 #Javascript
BootStrap智能表单实战系列(十一)级联下拉的支持
Jun 13 #Javascript
BootStrap 智能表单实战系列(十)自动完成组件的支持
Jun 13 #Javascript
You might like
提高PHP编程效率 引入缓存机制提升性能
2010/02/15 PHP
深入array multisort排序原理的详解
2013/06/18 PHP
PHP 5.5 创建和验证哈希最简单的方法详解
2013/11/07 PHP
php无限遍历文件夹示例分享
2014/03/04 PHP
thinkphp在模型中自动完成session赋值示例代码
2014/09/09 PHP
PHP获取日期对应星期、一周日期、星期开始与结束日期的方法
2018/06/22 PHP
jQuery News Ticker 基于jQuery的即时新闻行情展示插件
2011/11/05 Javascript
深入理解Javascript中的循环优化
2013/11/09 Javascript
基于jquery固定于顶部的导航响应浏览器滚动条事件
2014/11/02 Javascript
freemarker判断对象是否为空的方法
2015/08/13 Javascript
浅谈javascript的Array.prototype.slice.call
2015/08/31 Javascript
JS弹性运动实现方法分析
2016/12/15 Javascript
深入理解js中的加载事件
2017/02/08 Javascript
JS实现DIV高度自适应窗口示例
2017/02/16 Javascript
Ionic + Angular.js实现验证码倒计时功能的方法
2017/06/12 Javascript
详解Angular 中 ngOnInit 和 constructor 使用场景
2017/06/22 Javascript
如何抽象一个Vue公共组件
2017/10/17 Javascript
基于vue-router 多级路由redirect 重定向的问题
2018/09/03 Javascript
解决vue 单文件组件中样式加载问题
2019/04/24 Javascript
为vue项目自动设置请求状态的配置方法
2019/06/09 Javascript
Python中装饰器高级用法详解
2017/12/25 Python
Python(PyS60)实现简单语音整点报时
2019/11/18 Python
HTML5+lufylegend实现游戏中的卷轴
2016/02/29 HTML / CSS
澳大利亚正品化妆品之家:Cosmetic Capital
2017/07/03 全球购物
工商管理本科毕业生求职信范文
2013/10/05 职场文书
电脑销售顾问自荐信
2014/01/29 职场文书
冰淇淋开店创业计划书
2014/02/01 职场文书
西门豹教学反思
2014/02/04 职场文书
爱心倡议书范文
2014/05/12 职场文书
促销活动总结模板
2014/07/01 职场文书
大学生军训自我鉴定范文
2014/09/18 职场文书
打架赔偿协议书范本
2014/10/26 职场文书
商铺租房协议书范本
2014/12/04 职场文书
2015年收银工作总结范文
2015/04/01 职场文书
检察院起诉意见书
2015/05/20 职场文书
导游词之包公祠
2019/11/25 职场文书