jQuery.data的是jQuery的數據緩存系統。它的主要作用就是為普通對象或者DOM元素添加數據。 1 內部存儲原理 這個原理很簡單,原本要添加在DOM元素本身的數據,現在被集中的存儲在cache集合中。它們之間靠一個從1開始的數字鍵來聯繫著。這樣DOM元素就不會像以前那麼笨重了,更不會出現以... ...
jQuery.data的是jQuery的數據緩存系統。它的主要作用就是為普通對象或者DOM元素添加數據。
1 內部存儲原理
這個原理很簡單,原本要添加在DOM元素本身的數據,現在被集中的存儲在cache集合中。它們之間靠一個從1開始的數字鍵來聯繫著。這樣DOM元素就不會像以前那麼笨重了,更不會出現以前那種迴圈引用而引起的記憶體泄漏。現在DOM只需要保存好這個數字鍵值即可。這個屬性值被保存在DOM元素的一個屬性里,該屬性名是由jQuery.expando生成的。
2 Data構造函數
Object.defineProperty( this.cache = {}, 0, { get: function() { return {}; } });
首先來看Object.defineProperty函數,它的作用為:將屬性添加到對象,或修改現有屬性的特性。那我們先來看下ECMAScript5中的屬性。
2.1 ECMAScript5中的屬性
ECMAScript5中有兩種屬性:數據屬性和訪問器屬性。
2.1.1 數據屬性:
數據屬性包含一個數據值的位置,在這個位置可以讀取和寫入值。有4種特性用來限制其行為。
①[[configurable]]
能否通過delete刪除屬性,能否修改屬性的特性,能否把屬性改為訪問器屬性。預設為true。
var obj={name:"abc"}; Object.defineProperty(obj,"name",{ configurable:false }); console.log(obj.name); delete obj.name; console.log(obj.name); Object.defineProperty(obj,"name",{ configurable:true });
註意,一旦將 configurable 設為false,就再也設置不回去了。而且還會報錯。
②[[enumerable]]
在for-in迴圈是否能獲取到屬性。預設為true。ECMAScript規定,由程式員定義的屬性的該特性都為true。
③[[writable]]
能否修改屬性的值。預設為true。
var obj={name:"abc"}; Object.defineProperty(obj,"name",{ writable:false }); console.log(obj.name); obj.name="111"; console.log(obj.name);
④[[value]]
屬性值所在的地方。讀取屬性值的時候獲取的就是它,寫屬性值的時候也是往這裡寫。
var obj={name:"abc"}; console.log(obj.name); Object.defineProperty(obj,"name",{ value:111 }); console.log(obj.name);
2.1.2訪問器屬性
訪問器屬性不包含實際的屬性值,它包含兩個函數(getter和setter)
①[[get]]
讀取屬性時調用的函數。預設為undefined
②[[set]]
寫入屬性時調用的函數。預設為undefined
var obj={}; Object.defineProperties(obj,{ name:{ get:function () { return obj["_name"]; }, set:function (name) { if(name != "C#"){ obj["_name"]=name; } } }, label:{ get:function () { return "你不能改變我!!!"; } } }); obj.name="JavaScript"; console.log(obj.name); obj.label="我偏要改變你!!!"; console.log(obj.label);
上面給cache的屬性0,只給了get屬性。所以不能為該屬性賦值。只能獲取。
this.expando = jQuery.expando + Math.random();
這個expando就是用來為DOM元素或者對象存儲在cache中的鍵的。即它將作為DOM元素的一個屬性被存儲這。
紅色畫框內的屬性名就是由上面的語句生成的,指明div1的屬性存儲在cache的1屬性中。
Data.uid = 1;該屬性表示cache的屬性將從1開始自增。因為0已經被這個凍結的空JSON占用了,所以從1開始。我們下次再為某DOM元素添加屬性時,它將被保存在cache的2屬性中。
3 允許的添加屬性的元素
Data.accepts = function( owner ) { return owner.nodeType ? owner.nodeType === 1 || owner.nodeType === 9 : true; };
這段代碼寫的非常幹練。表示如果owner是DOM元素則只有ELEMENT_NODE和DOCUMENT_NODE兩種元素能添加屬性,如果owner是一個對象,則都可以添加屬性。
4 原型屬性
4.1 key: function( owner ) {
if ( !Data.accepts( owner ) ) { return 0; }
如果owner不能被添加data,則返回cache的第0個元素。
unlock = owner[ this.expando ];
這裡獲取的就是
如果能從owner中找到這個屬性,則說明以前為它添加過值,也就是說,它已經擁有了一個在cache中的key。
if ( !unlock ) { unlock = Data.uid++; try { descriptor[ this.expando ] = { value: unlock}; Object.defineProperties( owner, descriptor ); } catch ( e ) { descriptor[ this.expando ] = unlock; jQuery.extend( owner, descriptor ); } }
如果找不到,則說明是第一次為owner添加屬性,則要創建一個key。這個key就是在Data的uid的基礎上加1.Data的uid是一個靜態屬性。這樣就能記錄前一個元素的key是多少,這次的key又應該是多少。try塊裡面為owner(即DOM元素或者對象)添加它在cache中的索引。這裡會出現相容性的問題。在Android系統<4時會出現安全問題,因此jQuery使用extend靜態方法將descriptor擴展到owner上面。
if ( !this.cache[ unlock ] ) { this.cache[ unlock ] = {}; }
這裡為owner對應在cache中的key賦值一個Object。
這個的key方法的作用就是為DOM元素創建屬性,為cache中對應的key賦值(空對象)。
key返回的是這個unlock,即owner對象在cache中對應的key。
4.2 set: function( owner, data, value ) {
unlock = this.key( owner ), cache = this.cache[ unlock ];第一句不用解釋,第二句或者cachekey值。以上圖所示,則這裡的cache目前還是{},因為在此之前還沒有為div1添加過屬性。
if ( typeof data === "string" ) { cache[ data ] = value;
我們下麵的代碼走的就是這裡:
$("#div1").data("name","div1");
但是我們有很多屬性要設置的時候,
$("#div1").data({name:"div1",from:"0",to:"100"});
jQuery的處理方式是這樣的:
// 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 ]; } } }
其實這裡個人以為不用再判斷了,因為extend裡面也是用for迴圈將data的屬性擴展到cache中的。
4.3 get: function( owner, key ) {
var cache = this.cache[ this.key( owner ) ]; return key === undefined ? cache : cache[ key ];
獲取屬性,代碼非常簡單。如果key不存在則返回cache對象。
4.4 access: function( owner, key, value ) {
這裡對get和set方法的統一訪問。
4.5 remove: function( owner, key ) {
unlock = this.key( owner ), cache = this.cache[ unlock ];
獲取owner的在cache中的數據對象,this.key返回的是owner在cache中的key。
if ( key === undefined ) { this.cache[ unlock ] = {};
如果不指定要刪除那個屬性的話,jQuery會刪除owner所有的數據屬性。
否則再判斷key是不是數組,
if ( jQuery.isArray( key ) ) { name = key.concat( key.map( jQuery.camelCase ) ); }是數組的話,將key數組和用key的每一項轉駝峰後的數組合併,即:
$("#div1").remove(["one_key","two_key"]);
經過上面的代碼,key為變為["one_key","two_key","oneKey","twoKey"]。後面會將這4個屬性都刪除掉。
那如果key不是數組:
camel = jQuery.camelCase( key ); if ( key in cache ) { name = [ key, camel ]; } else { name = camel; name = name in cache ? [ name ] : ( name.match( core_rnotwhite ) || [] ); }
同樣先輸轉駝峰,然後判斷key是否存在,不存在則判斷key的駝峰形式是否存在,後面的正則表達式用於去除駝峰形式前後的空格。
當這些情況過濾完之後,進行刪除操作:
i = name.length; while ( i-- ) { delete cache[ name[ i ] ]; }
在while迴圈裡面使用delete進行刪除。
4.6 hasData: function( owner ) {
cache中是否擁有woner的數據對象。
4.7 discard: function( owner ) {
刪除cache中的woner的數據對象。
5 創建兩個私有的cache
data_user = new Data(); data_priv = new Data();所以,我們在jQuery的外面不能直接拿到這個cache。因為它是jQuery的局部變數。data_user供開發人員使用,data_priv供jQuery內部使用。
6 創建對外介面(工具方法和原型方法)
由於Data構造器是jQuery私有的,我們在外面不能訪問到,所以前面的那些方法,我們也不能直接訪問,jQuery在這裡,給我們提供了一些介面。來操作data_user,為DOM元素和Object進行屬性操作。工具方法非常簡單隻是對data_user方法的封裝而已。我們主要看下原型方法。
jQuery.fn.extend({ data: function( key, value ) {
設值和取值都會進入上面的方法。
這裡有這樣一個思想,如果是設值的時候,則給選集中所有的選項設值,如果獲取值的時候,只獲取第一個選項的值。
if ( key === undefined ) { if ( this.length ) { data = data_user.get( elem ); if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { attrs = elem.attributes; for ( ; i < attrs.length; i++ ) { name = attrs[ i ].name; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.slice(5) ); dataAttr( elem, name, data[ name ] ); } } data_priv.set( elem, "hasDataAttrs", true ); } } return data; }
在第3行,已經取到cache中elem對應的屬性了,下麵jQuery由將elem的attributes里的所有屬性添加到elem的 。
hasDataAttrs屬性是我們自己添加,因為下麵這段代碼只需要執行一次即可。代碼第6行,獲取elem所有的屬性;
這個NamedNodeMap類型,我們平時很少直接用它,它和NodeList,HTMLCollection一樣都是“動態”的。NamedNodeMap集合中的每一項都是Attr類型,Attr對象有3個屬性:name,value和specified。
在下麵的for迴圈裡面,就檢測這個name屬性中是否含有"data-"首碼。有的話就去掉它,並且將剩餘部分轉為駝峰形式。
如果key不存在,則表示獲取選集中第一個選項的所有屬性值。elem表示第一個選項。
if ( typeof key === "object" ) { return this.each(function() { data_user.set( this, key ); }); }對應這種形式:
$("#div1").remove(["one_key","two_key"]);