当前位置: 首页 > news >正文

jQuery数据赋值解析

作者:Jiang, Jinlin

在我们使用jQuery中,会用到$(element).data()方法存取赋值到元素上的数据。通过该方法,我们每次通过selector获取该元素时,总能获取其赋值的数据。今天,我们就来看看jQuery是如何实现的。

首先,我们先从data方法看起:


data方法接受两种参数形式,第一种通过key,value形式将数据赋值于元素之上。第二种通过key形式获取赋值的数据。(其中,如果key为空则导出所有key-value map)

通过data方法不但可以获取通过data本身传入的数据,也可以直接获取元素上直接[data-*]形式赋值的数据。接下来,就让我们看一看jQuery的源代码实现(我会在代码中进行标注,当然如果你不愿意看细节,之后会有一个图示来标示jQuery的数据赋值):


jQuery.fn.extend({

         data: function( key, value ) {

                   var i, name, data,

                            elem = this[0],

                            // 如果存在元素则获取它的attributes属性

                            attrs = elem && elem.attributes;

 

                   // Special expections of .data basically thwart jQuery.access,

                   // so implement the relevant behavior ourselves

 

                   // Gets all values

                   // 如果没有设定key,则将元素包含数据全部导出

                   if ( key === undefined ) {

                            if ( this.length ) {

                                     // 获取元素数据,之后会进入分析

                                     data = jQuery.data( elem );

 

                                     // nodeType属性用于获取类型,其中1DOM元素类型。因而其只会返回dom元素赋值的数据

                                     // _data为内部使用方法,之后将会详细介绍

                                     if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {

                                               i = attrs.length;

                                               while ( i-- ) {

 

                                                        // Support: IE11+

                                                        // The attrs elements can be null (#14894)

                                                        if ( attrs[ i ] ) {

                                                                 name = attrs[ i ].name;

                                                                 // 检查是否存在以data-开头的属性

                                                                 if ( name.indexOf( "data-" ) === 0 ) {

                                                                           // 使用驼峰法表示属性名,例如data-user-mail,将会被转成userMail

                                                                           name = jQuery.camelCase( name.slice(5) );

                                                                           // 获取[data-*]值并赋值数据

                                                                           dataAttr( elem, name, data[ name ] );

                                                                 }

                                                        }

                                               }

                                               // 标示该元素已经获取了[data-*]值,之后添加的data-*属性将不再获取

                                               // 你可以进行如下代码尝试一下结果:

                                               // $(ele).attr("data-test", 123);

                                               // $(ele).data(); => {test: 123}

                                               // $(ele).attr("data-test", 321);

                                               // $(ele).data(); => {test: 123}

                                               jQuery._data( elem, "parsedAttrs", true );

                                     }

                            }

 

                            return data;

                   }

 

                   // 如果传入的是一个object对象,则将其包含的所有key-value传入

                   // Sets multiple values

                   if ( typeof key === "object" ) {

                            return this.each(function() {

                                     jQuery.data( this, key );

                            });

                   }

 

                   return arguments.length > 1 ?

                            // 对所有元素赋值数据(应该尽量避免选择器同时选择多个进行数据赋值)

                            // Sets one value

                            this.each(function() {

                                     jQuery.data( this, key, value );

                            }) :

                            // 返回赋值数值

                            // Gets one value

                            // Try to fetch any internally stored data first

                            elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;

         },

});


jQuery的data与_data方法调用的都是调用内部的internalData方法,只是参数不同:

jQuery.extend({

         cache: {},

         data: function( elem, name, data ) {

                   return internalData( elem, name, data );

         },

         // For internal use only.

         _data: function( elem, name, data ) {

                   return internalData( elem, name, data, true);

         },

});

而dataAttr方法:

function dataAttr( elem, key, data ) {

         // 如果内部没有发现数据,则尝试获取html5[data-*]属性的数值

         // If nothing was found internally, try to fetch any

         // data from the HTML5 data-* attribute

         if ( data === undefined && elem.nodeType === 1 ) {

                   // 将驼峰法转换成data-*-*形式

                   var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();

 

                   data = elem.getAttribute( name );

 

                   if ( typeof data === "string" ) {

                            try {

                                     // string值进行类型转换,所以如果值符合转换条件就会被转掉

                                     data = data === "true" ? true :

                                               data === "false" ? false :

                                               data === "null" ? null :

                                               // Only convert to a number if it doesn't change the string

                                               +data + "" === data ? +data :

                                               rbrace.test( data ) ? jQuery.parseJSON( data ) :

                                               data;

                            } catch( e ) {}

 

                            // Make sure we set the data so it isn't changed later

                            // 将该值存入元素的data缓存

                            jQuery.data( elem, key, data );

 

                   } else {

                            data = undefined;

                   }

         }

 

         return data;

}

接着就是internalData,逻辑比较复杂。在此之前,我们先看一下相关的函数:

// jQuery初始化时,会创建一个随机的key值,这个值将会用于元素的内置jQuery mapping

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

 

// 可以接受数据赋值的mapping

noData: {

         "applet ": true,

         "embed ": true,

         // ...but Flash objects (which have this classid) *can* handle expandos

         "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"

},

 

/**

 * Determines whether an object can have data

 */

jQuery.acceptData = function( elem ) {

         // 检查元素类型是否接受数据赋值

         var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ],

         // 获取nodeType,如果没有(js object等)则将其认为是dom元素

                   nodeType = +elem.nodeType || 1;

 

         // 对元素类型进行判断,dom元素或者document或者flash object可以接受数据赋值

         // Do not set data on non-element DOM nodes because it will not be cleared (#8335).

         return nodeType !== 1 && nodeType !== 9 ?

                   false :

 

                   // Nodes accept data unless otherwise specified; rejection can be conditional

                   !noData || noData !== true && elem.getAttribute("classid") === noData;

};

 

然后是internalData:

function internalData( elem, name, data, pvt /* Internal Use Only */ ) {

         // 元素是否接受数据赋值(见上)

         if ( !jQuery.acceptData( elem ) ) {

                   return;

         }

 

         var ret, thisCache,

                   internalKey = jQuery.expando,//见上

 

                   // jQuery会分开处理js objectdom元素的数据赋值(因为IE 6-7GC bug

                   // We have to handle DOM nodes and JS objects differently because IE6-7

                   // can't GC object references properly across the DOM-JS boundary

                   isNode = elem.nodeType,

 

                   // Only DOM nodes need the global jQuery cache; JS object data is

                   // attached directly to the object so GC can occur automatically

                   // 重点!如果是dom元素则使用jQuerycache( jQuery使用global cache来保存dom元素赋值的数据)

                   cache = isNode ? jQuery.cache : elem,

 

                   // Only defining an ID for JS objects if its cache already exists allows

                   // the code to shortcut on the same path as a DOM node with no cache

                   // 获取元素内部id,如果不是dom元素则直接以internalKey作为id

                   id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;

 

         // Avoid doing any more work than we need to when trying to get data on an

         // object that has no data at all

         if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {

                   return;

         }

 

         // 如果没有id则赋予id

         if ( !id ) {

                   // Only DOM nodes need a new unique ID for each element since their data

                   // ends up in the global cache

                   if ( isNode ) {

                            // jQuery内置了一个deletedIds数组用于存储被弃用的id,如果有弃用的id则会被复用

                            id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;

                   } else {

                            id = internalKey;

                   }

         }

 

         // 如果没有cache则创建。对于js object,会添加一个toJSON的空方法以阻住JSON.stringify将容器内数据一同转换

         if ( !cache[ id ] ) {

                   // Avoid exposing jQuery metadata on plain JS objects when the object

                   // is serialized using JSON.stringify

                   cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };

         }

 

         // 允许直接传入一个object将其数据添加到cache

         // An object can be passed to jQuery.data instead of a key/value pair; this gets

         // shallow copied over onto the existing cache

         if ( typeof name === "object" || typeof name === "function" ) {

                   if ( pvt ) {

                            cache[ id ] = jQuery.extend( cache[ id ], name );

                   } else {

                            cache[ id ].data = jQuery.extend( cache[ id ].data, name );

                   }

         }

 

         thisCache = cache[ id ];

 

         // jQuery的缓存结构:{data: {}},用户存储的数据会被存于data中,从而避免和jQuery保存的数据冲突

         // jQuery data() is stored in a separate object inside the object's internal data

         // cache in order to avoid key collisions between internal data and user-defined

         // data.

         if ( !pvt ) {

                   if ( !thisCache.data ) {

                            thisCache.data = {};

                   }

 

                   thisCache = thisCache.data;

         }

 

         // 保存数据,同样会使用驼峰法将key改写

         if ( data !== undefined ) {

                   thisCache[ jQuery.camelCase( name ) ] = data;

         }

 

         // 获取数据,会返回原key的值,如果没有则返回驼峰法后key的值。如果key不是string类型,直接返回所有赋值数据

         // Check for both converted-to-camel and non-converted data property names

         // If a data property was specified

         if ( typeof name === "string" ) {

 

                   // First Try to find as-is property data

                   ret = thisCache[ name ];

 

                   // Test for null|undefined property data

                   if ( ret == null ) {

 

                            // Try to find the camelCased property

                            ret = thisCache[ jQuery.camelCase( name ) ];

                   }

         } else {

                   ret = thisCache;

         }

 

         return ret;

}

 

我们看一下简化的流程图:

插入单个key,value

插入key-value 集合

按key获取value


获取全部key-value集合



看过了jQuery的数据赋值思路后,我们也可以实现一个简单的版本:

var cache = [];

var _innerID = 0;

 

function data(ele, key, value) {

         if(!ele._innerID) {

                   ele._innerID = ++_innerID;

         }

 

         var _cache = cache[ele._innerID] = cache[ele._innerID] || {};

 

         if(key === undefined) {

                   return _cache;

         } else if(value === undefined) {

                   return _cache[key];

         } else {

                   _cache[key] = value;

         }

}

 

总结

jQuery数据赋值的实现分成dom元素和非dom元素两种。两者除了缓存容器选取不同外,存取逻辑是公用的。其中对于dom元素,会额外处理一次html5的[data-*]数据赋值。但是就如前所说的,当data-*数据被存入缓存容器后,jQuery便不会再次处理。因而通过data-*更新的数据将不会被$.fn.data获取到。

 

因而,我建议除非由于页面初始化赋值,否则应该尽量避免动态调整[data-*]赋值,而转用jQuery的data方法存取赋值。



相关文章:

  • Apache Kylin的快速数据立方体算法——概述
  • eBay RUM实践
  • 基于数理统计分析的服务器端持续性能压力测试方案
  • 支付结果通知机制研究
  • Apache Eagle:eBay开源分布式实时Hadoop数据安全引擎
  • Ebay开源:Eclipse Plugin Repository Portal
  • eBay WebRex: 动态web资源优化工具
  • MapOutputBuffer理解的三重境界
  • Druid at Pulsar
  • AngularJS渲染性能分析
  • Ebay开源基于大数据的可视化框架:Pulsar Reporting
  • JavaScript 异步原理
  • 从数据仓库到数据视图
  • Griffin – 模型驱动的数据质量服务平台
  • 细数Kubernetes Service那些事-kubernetes 服务发布以及在eBay的实践
  • angular组件开发
  • Date型的使用
  • Electron入门介绍
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • Java新版本的开发已正式进入轨道,版本号18.3
  • js中forEach回调同异步问题
  • MySQL的数据类型
  • Mysql优化
  • Python学习之路13-记分
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • Stream流与Lambda表达式(三) 静态工厂类Collectors
  • 机器人定位导航技术 激光SLAM与视觉SLAM谁更胜一筹?
  • 每天10道Java面试题,跟我走,offer有!
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 原生Ajax
  • ​2021半年盘点,不想你错过的重磅新书
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (C++20) consteval立即函数
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (三)docker:Dockerfile构建容器运行jar包
  • (转)jQuery 基础
  • ******IT公司面试题汇总+优秀技术博客汇总
  • .NET Core 实现 Redis 批量查询指定格式的Key
  • .NET 设计模式—适配器模式(Adapter Pattern)
  • .net反编译工具
  • .net访问oracle数据库性能问题
  • .net知识和学习方法系列(二十一)CLR-枚举
  • @for /l %i in (1,1,10) do md %i 批处理自动建立目录
  • @private @protected @public
  • []T 还是 []*T, 这是一个问题
  • [120_移动开发Android]008_android开发之Pull操作xml文件
  • [AIGC] Redis基础命令集详细介绍
  • [Android 13]Input系列--获取触摸窗口
  • [android] 请求码和结果码的作用
  • [Android]常见的数据传递方式
  • [BetterExplained]书写是为了更好的思考(转载)