摘要:未指定時(shí)直接返回整個(gè)對象,否則返回。鍵名也要轉(zhuǎn)為駝峰命名。在內(nèi),先對和兩個(gè)調(diào)用情況進(jìn)行處理。則遍歷對象,刪除在每個(gè)節(jié)點(diǎn)上的緩存數(shù)據(jù)。
jQuery源碼學(xué)習(xí)之data
jQuery中有兩個(gè)關(guān)于data操作的方法
$().data() $.data(elem);
內(nèi)部其實(shí)現(xiàn)均離不開自定義類Data
內(nèi)部類 DataData在src/data/Data.js定義,構(gòu)建時(shí)為實(shí)例添加expando屬性,作為唯一標(biāo)識(shí)
function Data() { this.expando = jQuery.expando + Data.uid++; }
在原型上添加了多個(gè)方法
Data.prototype = { cache: function(){ ... }, set: function(){ ... }, get: function(){ ... }, access: function(){ ... }, remove: function(){ ... }, hasData: function(){ ... } }
在jq內(nèi)部,使用cache方法獲取緩存的數(shù)據(jù)。傳入一個(gè)參數(shù)owner,表示要獲取緩存數(shù)據(jù)的對象。判斷在owner上是否有expando屬性,如果沒有,說明這個(gè)owner是否第一次調(diào)用,需要在其初始化緩存數(shù)據(jù)對象。判斷節(jié)點(diǎn)的類型,如果是元素節(jié)點(diǎn)或者document節(jié)點(diǎn)或者對象時(shí),可以設(shè)置緩存數(shù)據(jù)。如果是元素節(jié)點(diǎn)或者document節(jié)點(diǎn),直接使用對象字面量進(jìn)行賦值,屬性名是expando,值為空對象。如果是對象的話,使用Object.defineProperty為其定義數(shù)據(jù),屬性名也是expando,初始化為{},同時(shí)屬性描述符可以更改,不可枚舉。
Data.prototype.cachecache: function( owner ) { // Check if the owner object already has a cache // 獲取在owner的緩存值 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. // 判斷owener類型 是否能在其上調(diào)用data // 在元素節(jié)點(diǎn)或body或?qū)ο笊峡梢栽O(shè)置data // 其他節(jié)點(diǎn)不設(shè)置緩存數(shù)據(jù) if ( acceptData( owner ) ) { // If it is a node unlikely to be stringify-ed or looped over // use plain assignment // 此處為owner添加屬性 key為Data對象的expando值 建立owner和Data對象之間的連接 // owner是元素節(jié)點(diǎn)或body 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 // owner是對象 // 為owner添加expando屬性 初始化為{},同時(shí)屬性描述符可以更改,不可枚舉 } else { Object.defineProperty( owner, this.expando, { value: value, configurable: true } ); } } } return value; }
使用set來更新緩存對象,分為data(key,value)或data(obj)兩種調(diào)用情況,保存時(shí)要將鍵名保存為駝峰命名法。
Data.prototype.setset: 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; }
使用get來獲取緩存對象,調(diào)用時(shí)有data(key)和data()。未指定key時(shí)直接返回整個(gè)cache對象,否則返回cache[key]。鍵名也要轉(zhuǎn)為駝峰命名。
Data.prototype.getget: function( owner, key ) { return key === undefined ? this.cache( owner ) : // Always use camelCase key (gh-2257) owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ]; }
對調(diào)用的方式進(jìn)行區(qū)分,內(nèi)部調(diào)用 set或get
通過參數(shù)的數(shù)量和類型進(jìn)行區(qū)分:
key為空時(shí),獲取整個(gè)cache對象
key類型為string且value===undefined 對應(yīng)獲取指定值
其他調(diào)用均為set,在set內(nèi)部進(jìn)行區(qū)分
Data.prototype.accessaccess: 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; }
使用remove來刪除緩存對象屬性,調(diào)用時(shí),可以傳入一個(gè)string,表示要?jiǎng)h除的鍵名,或者傳入一個(gè)保存多個(gè)鍵名的string數(shù)組。鍵名也要轉(zhuǎn)為駝峰命名。如果不傳入出參數(shù),則直接刪除掉在owner上的緩存數(shù)據(jù)對象。
Data.prototype.removeremove: 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 ( Array.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( rnothtmlwhite ) || [] ); } 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 ]; } } }
判斷owner上是否有緩存數(shù)據(jù)。
Data.prototype.hasDatahasData: function( owner ) { var cache = owner[ this.expando ]; return cache !== undefined && !jQuery.isEmptyObject( cache ); }jQuery方法的定義
定義后內(nèi)部類data后,在/src/data.js進(jìn)行拓展。在jQuery添加了hasData、data、 removeData、_data、_removeData等方法,在jQuery.fn上添加了data和removeData方法。
在jQuery拓展的方法,都是對Data方法的封裝,在調(diào)用時(shí)$.data()時(shí),并無對set和get操作區(qū)分,在Data.prototype.access內(nèi)部區(qū)分set和get。下面源碼中,dataUser和dataPriv是Data實(shí)例,分別為緩存數(shù)據(jù)和表示jq對象是否將元素的data屬性添加到dataUser中
// $.data 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.fn上拓展的方法只有data和removeData。在data內(nèi),先對$().data()和$().data({k:v})兩個(gè)調(diào)用情況進(jìn)行處理。如果是第一種情況,則返回在this[0]上的緩存數(shù)據(jù)對象,如果是第一次以$().data()的方式調(diào)用,同時(shí)還會(huì)將元素上的data屬性添加dataUser中,并更新dataPriv。如果是$().data({k:v})的調(diào)用方式,則遍歷jq對象,為每個(gè)節(jié)點(diǎn)更新緩存數(shù)據(jù)。其他調(diào)用方式如$().data(k)和$().data(k,v)則調(diào)用access進(jìn)行處理。此處的access并非Data.prototype.access。
removeData則遍歷jq對象,刪除在每個(gè)節(jié)點(diǎn)上的緩存數(shù)據(jù)。
// $().data jQuery.fn.extend( { data: function( key, value ) { var i, name, data, elem = this[ 0 ], // elem為dom對象 attrs = elem && elem.attributes; // 節(jié)點(diǎn)上的屬性 // Gets all values // $().data() if ( key === undefined ) { if ( this.length ) { data = dataUser.get( elem ); // elem是元素節(jié)點(diǎn),且dataPriv中無hasDataAttrs時(shí)執(zhí)行這個(gè)代碼塊里的代碼 // dataPriv上的hasDataAttrs表示elem是否有data-xxx屬性 // 初始化dataPriv后花括號(hào)內(nèi)的代碼不再執(zhí)行,即以下的if內(nèi)的代碼只執(zhí)行一次 if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { i = attrs.length; while ( i-- ) { // Support: IE 11 only // The attrs elements can be null (#14894) // 將elem的data-*-*屬性以*-*轉(zhuǎn)為駝峰命名方式的值為鍵 // 在dataAttr函數(shù)內(nèi)保存到dataUser中 // 所以用$().data可以獲取元素的data-*屬性 但修改后dom上的屬性卻不變化 if ( attrs[ i ] ) { name = attrs[ i ].name; // name為data-xxx if ( name.indexOf( "data-" ) === 0 ) { // name為xxx name = jQuery.camelCase( name.slice( 5 ) ); dataAttr( elem, name, data[ name ] ); } } } // 同時(shí)將dataPriv的hasDataAttrs屬性設(shè)置為真,表示已經(jīng)將元素屬性節(jié)點(diǎn)上的data屬性保存到緩存對象中 dataPriv.set( elem, "hasDataAttrs", true ); } } return data; } // Sets multiple values // $().data(obj) 此處遍歷this,即遍歷jq對象上所有的節(jié)點(diǎn),并在其設(shè)置值 if ( typeof key === "object" ) { return this.each( function() { dataUser.set( this, key ); } ); } // 除了$().data()和$().data({k:v})的其他情況 return access( this, function( value ) { var data; // The calling jQuery object (element matches) is not empty // (and therefore has an element appears at this[ 0 ]) and the // `value` parameter was not undefined. An empty jQuery object // will result in `undefined` for elem = this[ 0 ] which will // throw an exception if an attempt to read a data cache is made. // value undefined說明是獲取操作 // 調(diào)用$().data(k) if ( elem && value === undefined ) { // Attempt to get data from the cache // The key will always be camelCased in Data // 從dataUser中獲取 非undefined時(shí)返回 data = dataUser.get( elem, key ); if ( data !== undefined ) { return data; } // Attempt to "discover" the data in // HTML5 custom data-* attrs // dataUser中不存在key,調(diào)用dataAttr查找元素的data-*屬性 // 如果存在屬性,更新dataUser并返回其值 data = dataAttr( elem, key ); if ( data !== undefined ) { return data; } // We tried really hard, but the data doesn"t exist. return; } // Set the data... // jq對象長度 >= 1, 調(diào)用如$().data(k,v) 遍歷jq對象,為每個(gè)節(jié)點(diǎn)設(shè)置緩存數(shù)據(jù) this.each( function() { // We always store the camelCased key dataUser.set( this, key, value ); } ); }, null, value, arguments.length > 1, null, true ); }, // 遍歷jq對象,刪除各個(gè)元素上的緩存數(shù)據(jù) removeData: function( key ) { return this.each( function() { dataUser.remove( this, key ); } ); } } );
其中,getData用于對元素上的data屬性進(jìn)行類型轉(zhuǎn)換,dataAttr用于獲取保存在元素節(jié)點(diǎn)上的data屬性,并同時(shí)更新dataUser。需要注意的是,以$().data(k, v)方式調(diào)用時(shí),如果在緩存數(shù)據(jù)上查找不到屬性,則會(huì)調(diào)用dataAttr在元素查找屬性。
// 屬性值是string 進(jìn)行類型轉(zhuǎn)換 function getData( data ) { if ( data === "true" ) { return true; } if ( data === "false" ) { return false; } if ( data === "null" ) { return null; } // Only convert to a number if it doesn"t change the string // data轉(zhuǎn)化成number再轉(zhuǎn)成string后仍嚴(yán)格等于data if ( data === +data + "" ) { return +data; } if ( rbrace.test( data ) ) { return JSON.parse( data ); } return data; } // 獲取元素的dataset中的屬性,并保存到dataUser中 function dataAttr( elem, key, data ) { var name; // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute // 此處獲取dataset里的值 if ( data === undefined && elem.nodeType === 1 ) { name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); // 此處將駝峰命名方式的key轉(zhuǎn)化為data-*-* data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = getData( data ); } catch ( e ) {} // Make sure we set the data so it isn"t changed later // 將元素的data-*屬性保存到dataUser中 dataUser.set( elem, key, data ); } else { data = undefined; } } return data; }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/96135.html
摘要:一起源方法最終是用綁定事件的而方法正是等于二作用觸發(fā)綁定的事件的處理程序源碼源碼行即原生觸發(fā)事件的處理程序修正對象獲取事件的處理程序集合,結(jié)構(gòu)如下從數(shù)據(jù)緩存中獲取事件處理集合即目標(biāo)元素委托目標(biāo)這段代碼壓根不會(huì)執(zhí)行,因?yàn)槿炙阉鳑]找到結(jié)構(gòu) showImg(https://segmentfault.com/img/remote/1460000019464031); 一、起源jQuery.e...
摘要:五作用的關(guān)鍵方法,用來從目標(biāo)節(jié)點(diǎn)克隆數(shù)據(jù)添加事件給克隆的元素注意采用數(shù)據(jù)分離的方法來保存上的事件和數(shù)據(jù),利用標(biāo)記每個(gè)元素,然后在內(nèi)存上,將每個(gè)元素相關(guān)的數(shù)據(jù)放到內(nèi)存中,然后在和內(nèi)存的數(shù)據(jù)之間建立映射。 showImg(https://segmentfault.com/img/remote/1460000018991125); 前言:這篇講完后,jQuery的文檔處理就告一段落了,有空我...
摘要:作為此時(shí)不存在,直接從數(shù)據(jù)緩存中獲取并返回。作用是觸發(fā)中的回調(diào)函數(shù),的表示只讓觸發(fā)一次后,就需要清理,表示是將清空成空數(shù)組還是空字符。 showImg(https://segmentfault.com/img/remote/1460000019558449); 前言:queue()方法和dequeue()方法是為 jQuery 的動(dòng)畫服務(wù)的,目的是為了允許一系列動(dòng)畫函數(shù)被異步調(diào)用,但不...
摘要:階段二目標(biāo)瀏覽器找到監(jiān)聽器后,就運(yùn)行該監(jiān)聽器階段三冒泡目標(biāo)到祖在事件自下而上到達(dá)目標(biāo)節(jié)點(diǎn)的過程中,瀏覽器會(huì)檢測不是針對該事件的監(jiān)聽器用來捕獲事件,并運(yùn)行非捕獲事件的監(jiān)聽器。注意下這種情況,是在里的具體實(shí)現(xiàn),即調(diào)用一次后,就執(zhí)行,卸載事件。 showImg(https://segmentfault.com/img/remote/1460000019304809); 前言:這篇依舊長,請耐...
閱讀 1178·2021-09-10 10:51
閱讀 909·2019-08-30 15:53
閱讀 2735·2019-08-30 12:50
閱讀 986·2019-08-30 11:07
閱讀 1998·2019-08-30 10:50
閱讀 3607·2019-08-29 18:47
閱讀 1319·2019-08-29 18:44
閱讀 1607·2019-08-29 17:01