data函數在jQuery中看起來很不起眼, 就像沙灘上一顆平凡的沙子, 但仔細一瞅, 卻驚訝的發現data是jQuery中無比重要的一環, 甚至jQuery中各類事件都基於此。javascript
在咱們平時js編碼過程當中,咱們常常會向DOM元素中添加各類自定義屬性,這樣有一個弊端。java
1 假設咱們在DOM元素中添加了一個屬性,這個屬性指向了某個js對象。 dom1.ele = jsObjnode
2 當這個js對象發揮完做用後,咱們已經用不到他了。這時候按理說應該把這個js變量清空,釋放內存。你們都知道,若是一個js對象不存在任何外在引用的話,解釋器會自動將其在內存中刪除,這也是javascript相對於c++等手動管理內存的程序的優勢。jquery
3 可是這時候問題來了,由於DOM元素引用了這個js對象,儘管這個js對象已經沒有存在的意義了,可是解釋器是不會把他刪除的。若是想要把其刪除,咱們可能須要將DOM元素的這個屬性設置爲null。c++
4 咱們編寫了這麼多的代碼,哪裏能把 每一個js對象是否是被DOM元素引用了都記住啊?git
5 並且,假如DOM元素與js對象之間相互循環引用,根本就沒法刪除! 這就是內存泄漏github
6 因此,爲了不這種狀況的發生,咱們要儘可能避免 引用數據(這裏的引用數據能夠說是javascript對象) 直接依附在DOM對象上。緩存
7 data就是用來搞定以上問題的方法。app
1 首先咱們建立一個數據緩存池,這個緩存池專門用來存儲 向 DOM對象或者jQuery對象附加的額外數據。less
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)是對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