ホームページ > 記事 > ウェブフロントエンド > jQuery.data、jQuery._data、データインスタンス関数の使い方と注意点を徹底分析
質問 1: jQuery 内に保存されるデータの形式は何ですか? data を通じて保存されたデータと登録されたイベントの形式の違いは何ですか?
まず次のコードを見てください:
$._data($("#data")[0],"name","qinliang"); $._data($("#data")[0],"sex","male"); $("#data").click(function() { console.log("click1"); }); $("#data").click(function() { console.log("click2"); }); $("#data").mouseover(function() { console.log("click"); }); //data保存的数据通过result.name,result.sex访问 //在DOM上面绑定的事件函数如果要访问,可以通过result.events.click,result.events.mouseover不过返回的都是数组 //特别注意:这里是通过_data方法保存的数据! var result=$._data($("#data")[0]); console.log(result);
注: この図から、次のことが分かります。 _data 経由 保存されたデータは DOM オブジェクトに保存されます。$._data(document.getElementById("data")) を通じて取得されたオブジェクトには、キー名を通じてのみアクセスできます。 $ ._data(document.getElementById("data")).events["click"] を通じてアクセスできます。
質問 2: $.data を通じてユーザーが保存したデータの形式は何ですか?
上記のコードを次のように変更します:
$.data($("#data")[0],"name","qinliang"); $.data($("#data")[0],"sex","male"); $("#data").click(function() { console.log("click1"); }); $("#data").click(function() { console.log("click2"); }); $("#data").mouseover(function() { console.log("click"); }); //data保存的数据通过result.name,result.sex访问 //在DOM上面绑定的事件函数如果要访问,可以通过result.events.click,result.events.mouseover不过返回的都是数组 //特别注意:这里是通过_data方法保存的数据! var result=$.data($("#data")[0]); console.log(result);
注: この図から、ユーザーが保存したデータがわかることがわかります。 data メソッドによって保存されるデータはカスタム データのみですが、イベント登録などの内部データはユーザー データの範囲内ではありません。
質問 3: これらのデータは jQuery の内部データであり、それらのデータはユーザー データです。定義されたデータ?
回答: たとえば、イベント登録または _data は jQuery を通じて内部的に呼び出されます。保存されるデータの形式は最初の画像であり、ユーザーが data を通じて保存したデータは 2 番目の画像の形式のみです。ユーザー定義のデータが含まれています!
質問 4: jQuery インスタンス にバインドしたデータはどのように最下層に保存されますか?その形式は、jQuery を介してデータを保存するのと同じですか?
上記の例をインスタンスに変更します。メソッド:
$("#data").data("name","qinliang"); $("#data").data("sex","male"); $("#data").click(function() { console.log("click1"); }); $("#data").click(function() { console.log("click2"); }); $("#data").mouseover(function() { }) //先获取jQuery.expando才能去获取钥匙,所以jQuery.expando是最重要的,他是打开大门的第一步! var internalKey=jQuery.expando; //通过jQuery.expando去获取钥匙 var id= $("#data")[0][internalKey]; //获取到钥匙以后,就可以去jQuery.cache这个仓库去获取到数据了! var result=jQuery.cache[id]; //获取到的数据 console.log(result);
注: この図から、インスタンス オブジェクトの下部に保存されるデータ形式には 3 つのフィールドがあることがわかります。データ フィールドはユーザーのデータを保存し、イベントはオブジェクトのイベントを保存します。 コールバック関数 !
質問 5: jQuery.expando とは一体何ですか?
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" )//每次保存数据的时候会生产一个expando?,具有随机性?
データを保存するたびに乱数を生成しますか? 実際、いいえ、最初に呼び出されたときに一度だけ生成されます。これは、 data メソッドを何回呼び出しても、値は同じであることに注意してください。
次のコードは、同じ乱数を生成します。
つまり、各 DOM は同じ属性名 jQuery.expando を持ちます!質問 5: Expando は一意であるため、どのようにして各 DOM が異なるキーを持つことができますか?
var result1=jQuery.expando; var result2=jQuery.expando; console.log(result1); console.log(result2);//result1和result2是相等的,因为只在第一次调用的时候产生一次随机数,以后就是直接访问前面的随机数了,因为是全局变量非函数!
質問 6: jQuery.guid とは何ですか? なぜ各 DOM 要素が一意のキーを持つようにできるのですか? プロキシ関数が新しい関数を生成するときに、guid を処理してその値をインクリメントします。 jQuery.event.add メソッドでは、GUID 値がない場合、バインドされた関数ごとに新しい GUID が設定されます。
if ( isNode ) { id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; } else { id = internalKey; }
関数のインスタンスで、関数が 1 回だけ呼び出される場合は、GUID 値を設定します (実際にはプロキシ関数)
proxy: function( fn, context ) { var args, proxy, tmp; if ( typeof context === "string" ) { tmp = fn[ context ]; context = fn; fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( !jQuery.isFunction( fn ) ) { return undefined; } // Simulated bind args = slice.call( arguments, 2 ); proxy = function() { return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || jQuery.guid++;//对新的函数设置guid++保证每一个通过jQuery处理的函数都有一个guid值,而且唯一! return proxy; }
インスタンスがデータを保存するとき、各DOMオブジェクトの同じプロパティ、つまりjQuery.expandoプロパティに一意のキー値を設定します:
if ( !handler.guid ) {//没有guid值添加 handler.guid = jQuery.guid++; }
note:j
Query.guidイベント バインディングとデータ ストレージに使用され、各保存データの DOM と各バインドされたイベントのコールバック関数に一意の guid 値を割り当てます。
質問 7: キーは取得されましたが、ウェアハウスはどこですか?
if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );//为函数都唯一生产guid,其它函数通过调用上面的jQuery.event.add保证唯一! }ここから、DOM オブジェクトの場合、ウェアハウスは jQuery.cache であり、js オブジェクトの場合、ウェアハウス自体であることがわかります! 質問 8: キーが取得され、ウェアハウスも使用可能になり、データは取得できますが、データはどこに保存されますか?
if ( isNode ) { id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; } else { id = internalKey; }
jQuery.cache={}, cache = isNode ? jQuery.cache : elem,注: データが存在する場合は、データを保存することを意味します。特定のドメイン。ユーザー データはウェアハウスのデータ フィールドの下にあり、内部データはウェアハウスに直接あります。データを取得する場合、名前が文字列の場合は、ウェアハウス内の特定のコンテンツを取得します。それ以外の場合は、直接ウェアハウスにあります。ウェアハウス自体に戻ってください! 質問 10: 上記の
が、jQuery.data メソッドは呼び出すことができます。このメソッドは Object オブジェクトにデータを保存することもできます。この方法でデータを保存します:
var obj=new Object(); jQuery.data(obj,"name","qinliang"); var key=jQuery.expando; //对于Object对象,钥匙就是jQuery.expando var warehouse=obj; //获取仓库,Object对象的仓库是自身 var data= warehouse[key]; //获取数据 console.log(data);
note:通过该图你会发现,通过object对象保存的数据也是在data域下面,同时为了保证数据的安全性,该元素的toJson方法是空的,也表示无法对数据进行json格式化!
问题11:如果是jQuery内部的数据那么不是保存在data域下面?我想确认一下?
//内部数据是只能通过jQuery._data自己调用 jQuery._data($("#data")[0],"name","qinliang"); jQuery._data($("#data")[0],"sex","male"); $("#data").click(function() { console.log("1"); }); //获取expando以求获取钥匙 var expando=jQuery.expando; //获取钥匙 var key=$("#data")[0][expando]; //获取仓库 var walhouse=jQuery.cache[key]; console.log(walhouse);
note:通过该图我们再次确认了,jQeury内部的数据都是单独保存的,没有放入data域下面进行重新组织,这一点一定要和用户自定义的数据区分开来!
问题12:内部数据和用户数据调用方式如何?
jQuery内部数据通过jQuery._data方法保存
_data: function( elem, name, data ) { return internalData( elem, name, data, true );//第四个参数为true表示保存在非data域下面! }
用户数据保存在data域下面:
data: function( elem, name, data ) { return internalData( elem, name, data ); }
问题13:我们调用实例方法如果需要设置多个属性和值,那么必须多次调用data方法?
解答:不是的,我们看看实例data方法是如何处理的
<span style="font-size:10px;">if ( typeof key === "object" ) { return this.each(function() { jQuery.data( this, key );//这说明我们如果要保存多个值,那么可以传入对象字面量 }); }</span>
那么我们在看看jQuery.data是如何支持实例方法传入对象字面量的
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 ); } }
note:jQuery.data支持对象字面量是通过jQuery.extend来实现的,他会把我们通过data实例方法传入的对象字面量全部继承到仓库中!
问题14:如果我们调用data方法不传入参数,或者传入参数是undefined,那是不是说返回的必定是undefined?
解答:不对,我们看看data实例方法是如何处理的
f ( key === undefined ) { if ( this.length ) { //获取第1个元素的数据 data = jQuery.data( elem ); //如果第1个调用元素的parsedAttrs属性为空! 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; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.slice(5) ); //通过dataAttr方法获取到HTML5数据 //传入如dataAttr(elem,"sex",undefined) dataAttr( elem, name, data[ name ] ); } } } jQuery._data( elem, "parsedAttrs", true ); } } return data; }
note:如果没有传入参数,那么首先获取第一个DOM元素的所有的属性,同时查找该DOM元素上同名的HTML5属性,把这个数据保存到jQuery内部数据中,键名是parsedAttrs!这一点是jQuery智能操作完成的!看下面的例子:
HTML部分:
<p style="width:100px;height:100px;background-color:red;" id="data" data-sex="male" data-name="qinliang"></p>
JS部分:
//这时候传入的参数是undefined,所以最后就是把HTML5的数据保存为用户的数据,键名为HTML5键名的 //缩写版,如"sex"而不是"data-sex"。键值就是HTML5数据的值! var result=$("#data").data(); var result1=$("#data").data("name"); console.log(result1);//打印qinliang var result2=$("#data").data("sex"); console.log(result2);//打印male
note:在这里我只想说,如果用户调用data方法时候没有传入任何参数,那么jQuery会把HTML5数据作为用户数据保存,所以下次再次获取数据的时候就可以获取到了!
jQuery是如何把HTML5数据保存为用户数据的呢?
function dataAttr( elem, key, data ) { // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute //如果传入的data是undefined,同时elemenet是DOM对象,那么我们就获取HTML5上的同名属性! if ( data === undefined && elem.nodeType === 1 ) { //如果是大写就满足这个正则表达式,那么会添加两个横线并且变成小写! var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { //如果是true,false,null字符串那么返回非字符串形式的数据,如果不是这几种类型 data = data === "true" ? true : data === "false" ? false : data === "null" ? null : // Only convert to a number if it doesn't change the string //如果是数字那么才会转化为数子,如果是JSON类型那么转化为JSON,否则就是原样返回! +data + "" === data ? +data : rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} //把我们获取到的数据保存为用户数据 // Make sure we set the data so it isn't changed later jQuery.data( elem, key, data ); } else { data = undefined; } } return data; }
note:我相信你不难看出,最后是通过jQuery.data把数据保存为用户数据的!注意,我们现在回到上面的那个不传入参数的情况,那么他其实是只保存了调用对象的第1个元素的数据作为用户数据,如果有多个DOM元素组成了调用对象,剩余的DOM对象是没有数据的,看下面的例子:
HTML部分
<p style="width:100px;height:100px;background-color:red;" class="data" data-sex="male" data-name="qinliang"></p> <p style="width:100px;height:100px;background-color:red;" class="data" id="me"></p>
JS部分:
//这时候传入的参数是undefined,所以最后就是把HTML5的数据保存为用户的数据,键名为HTML5键名的 //缩写版,如"sex"而不是"data-sex"。键值就是HTML5数据的值! var result=$(".data").data(); var result1=$(".data").data("name"); console.log(result1);//打印qinliang var result2=$(".data").data("sex"); console.log(result2);//打印male var result=$("#me").data(); var result1=$("#me").data("name"); console.log(result1);//打印undfined var result2=$("#me").data("sex"); console.log(result2);//打印undefined
note:虽然第一步$(".data")匹配了多个DOM元素,但是只是把第一个DOM元素的HTML5属性作为第一个DOM元素的内部数据,所以第二个DOM元素,也就是id为me的元素仍然没有name和sex属性值!
问题15:实例data源码中有parsedAttrs,是干嘛用的?
如果我们调用data方法但是没有传入参数,这时候就会存在parsedAttrs了
jQuery._data( elem, "parsedAttrs", true );//作为jQuery内部数据
其值作为内部数据保存
//这时候传入的参数是undefined,所以最后就是把HTML5的数据保存为用户的数据,键名为HTML5键名的 //缩写版,如"sex"而不是"data-sex"。键值就是HTML5数据的值! var result=$(".data").data(); //获取expando以备获取钥匙 var expando=jQuery.expando; //获取钥匙 var key=$(".data")[0][expando]; //获取仓库 var walhouse=jQuery.cache; //打开仓库获取数据 var result=walhouse[key]; console.log(result);
note:通过该图你要知道parseAttrs保存的是true,表示已经解析过HTML5属性了;但是注意:内部数据和外部数据虽然获取的方式是一样的,都是获取expando,获取钥匙,获取仓库,获取数据。但是你应该马上反应过来,上面的parseAttrs没有保存到data域下面,这就是他通过内部数据保存的结果。同时,你也应该看到内部数据和用户自定义数据在这个图上存在的共存的情况!
问题16:data如何体现了获取数据的逻辑?
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 //如果传入的参数不大于1,也就是传入单个值,因为不传值上面已经return了 //那么用dataAttr获取值,传入参数为第一个DOM对象,第二个参数是data方法传入的参数 //第三个参数是第一个DOM对象的所有通过$.data保存的数据 elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined; }
note:jQuery.data(elem,key)就是获取元素上指定键名的键值数据,如果键值存在不是undefined那么dataAttr不做任何处理,直接返回;如果键值不存在那么dataAttr就回去获取HTML5属性值,保存为用户数据的同时返回数据!所以下面这种情况是有值返回的。
HTML部分:
<p style="width:100px;height:100px;background-color:red;" id="data" data-name="qinliang"></p>
JS部分:
var result=$("#data").data("name"); console.log(result);
note:用户没有保存数据的情况下我们却获取到了数据,这就是dataAttr的功劳,他把数据保存为用户数据的同时把数据返回。
问题17:除了Object,DOM,还有没有其它对象可以用来保存数据?
看看筛选函数
if ( !jQuery.acceptData( elem ) ) { return; }
详细代码如下
noData: { "applet ": true, "embed ": true, // ...but Flash objects (which have this classid) *can* handle expandos "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" } jQuery.acceptData = function( elem ) { var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ], nodeType = +elem.nodeType || 1; // Do not set data on non-element DOM nodes because it will not be cleared (#8335). //如果元素不是Element同时也不是document那么直接返回false表示不能用来保存数据! return nodeType !== 1 && nodeType !== 9 ? false : //如果是元素或者为document对象可以保存数据;applet和embed不能保存数据;Flash可以保存数据! // Nodes accept data unless otherwise specified; rejection can be conditional !noData || noData !== true && elem.getAttribute("classid") === noData; };
note:Element对象,document对象,Flash对象可以保存数据,applet和embed不能保存数据!很显然JS对象是可以保存数据的,因为里面没有针对JS对象排除的代码!
问题18:如何判断一个元素是否有自己的数据?
hasData: function( elem ) { //如果是DOM对象,那么就是判断仓库是否为空的,不需要特别针对data域,elem[jQuery.expando] //是钥匙,jQuery.cache是仓库。如果是JS对象等其它对象,那么钥匙就是jQuery.expando,仓库就是自身! elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; //仓库非空,通过isEmptyObject来判断 return !!elem && !isEmptyDataObject( elem ); }
详细见isEmptyDataObject
function isEmptyDataObject( obj ) { var name; for ( name in obj ) { // if the public data object is empty, the private is still empty //如果data域是空的,那么继续判断下一个字段 if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { continue; } //如果有一个除了toJSON的字段,那么表示非空! if ( name !== "toJSON" ) { return false; } } return true; }
note:如果有一个除了toJSON的字段,那么表示这个数据对象不是空对象!
有了上面的18个问题,看jQuery.data或者jQuery._data的源码,或者实例data方法的源码应该不在话下了!源码如下:
jQuery.data方法源码
data: function( elem, name, data ) { return internalData( elem, name, data ); }
jQuery._data方法源码
_data: function( elem, name, data ) { return internalData( elem, name, data, true ); }
实例data方法源码
data: function( key, value ) { var i, name, data, //获取第1个调用元素 elem = this[0], //保存第一个调用元素的属性集合! attrs = elem && elem.attributes; // Special expections of .data basically thwart jQuery.access, // so implement the relevant behavior ourselves // Gets all values //没有传入key或者key为undefined if ( key === undefined ) { if ( this.length ) { //获取第1个元素的数据 data = jQuery.data( elem ); //如果第1个调用元素的parsedAttrs属性为空! 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; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.slice(5) ); //通过dataAttr方法获取到HTML5数据 //传入如dataAttr(elem,"sex",undefined) dataAttr( elem, name, data[ name ] ); } } } jQuery._data( elem, "parsedAttrs", true ); } } return data; } // 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; }
internalRemoveData源码:
function internalData( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var ret, thisCache, internalKey = jQuery.expando, // 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 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 = 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; } if ( !id ) { // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache if ( isNode ) { id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; } else { id = internalKey; } } 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 }; } // 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() 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; } if ( data !== undefined ) { thisCache[ jQuery.camelCase( name ) ] = data; } // 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; }
总结:
对于jQuery.data保存数据和获取数据严格遵守下面的次序:
第一步:获取expando以备用他来获取钥匙。expando=jQuery.expando;
第二步:通过expando获取到钥匙。JS对象:key=expando; DOM对象key=elem[expando]
第三步:获取仓库。JS对象walhouse=elem;DOM对象walhouse=jQuery.cache
第四步:用钥匙打开仓库。JS对象data=elem[expando],DOM对象data=walhouse[key]
第五步:获取数据,如果是用户数据要在data的data域下面,如果是内部数据不用到data域下面!
以上がjQuery.data、jQuery._data、データインスタンス関数の使い方と注意点を徹底分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。