/* Implementation Summary 1. Enforce API surface and semantic compatibility with 1.9.x branch 2. Improve the module's maintainability by reducing the storage paths to a single mechanism. 3. Use the same single mechanism to support "private" and "user" data. 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) 5. Avoid exposing implementation details on user objects (eg. expando properties) 6. Provide a clear path for implementation upgrade to WeakMap in 2014 */ var data_user, data_priv, rbrace = /(?:{[sS]*}|[[sS]*])$/, rmultiDash = /([A-Z])/g; function Data() { // cache = {0:{} },并且这个0属性是不能被修改的。这个0是公用的。 Object.defineProperty( this.cache = {}, 0, { get: function() { return {}; } }); //唯一标识,id this.expando = jQuery.expando + Math.random(); } /* var cache = { 1 : { //同一个元素只有一个id age : 30, job : 'it', 'allName' : '妙味课堂' }, 2 : { age : obj } */ Data.uid = 1;//cache中可以累加的属性1,2,3 Data.accepts = function( owner ) { // 1是元素,2是document,都是可以在cache中分配id的。 return owner.nodeType ? owner.nodeType === 1 || owner.nodeType === 9 : true; }; Data.prototype = { //分配映射id或者0:空json key: function( owner ) {//owner是document.body或者$('#div1')对象, // 不是元素也不是document,就返回0,id就是0,在cache中返回空对象{},defineProperty中给cache定义的空对象。 if ( !Data.accepts( owner ) ) { return 0; } var descriptor = {}, // 获取$('#div1')的属性this.expando的值, unlock = owner[ this.expando ]; if ( !unlock ) { unlock = Data.uid++; try { /* descriptor={jQuery203089541586732714850.8840931279098725:{value:1}} */ descriptor[ this.expando ] = { value: unlock }; //给owner增加一个属性descriptor:json并且不能改变 Object.defineProperties( owner, descriptor ); // Support: Android < 4 // Fallback to a less secure definition } catch ( e ) { //兼容性:通过extend给owner增加{key:value}的键值对,这个可以被修改descriptor={jQuery203089541586732714850.8840931279098725:1} descriptor[ this.expando ] = unlock; jQuery.extend( owner, descriptor ); } } if ( !this.cache[ unlock ] ) { this.cache[ unlock ] = {};//cache={1:{}} } return unlock; }, //设置cache的值 set: function( owner, data, value ) { var prop, //找到这个元素的id和cache中的json unlock = this.key( owner ), cache = this.cache[ unlock ];//cache是某个id的json if ( typeof data === "string" ) { cache[ data ] = value;//是字符串,就把key value加进去 //是json,$.data(document.body ,{ 'age' : 30 , 'job' : 'it' , 'allName' : '妙味课堂'}); } else { // Fresh assignments by object are shallow copied if ( jQuery.isEmptyObject( cache ) ) { jQuery.extend( this.cache[ unlock ], data );//继承json过去 // Otherwise, copy the properties one-by-one to the cache object } else { for ( prop in data ) { cache[ prop ] = data[ prop ]; } } } return cache; }, //获取cache中的值 get: function( owner, key ) { var cache = this.cache[ this.key( owner ) ]; return key === undefined ? cache : cache[ key ]; }, //set,get合体 access: function( owner, key, value ) { var stored; // 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) ) { //没有key全部取出来,有key没有value取key stored = this.get( owner, key ); return stored !== undefined ? stored : this.get( owner, jQuery.camelCase(key) ); } //有key有value.key是字符串全部覆盖,key是json追加 // [*]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; }, //移除cache remove: function( owner, key ) { var i, name, camel, unlock = this.key( owner ), cache = this.cache[ unlock ];//cache是某一个key的cache, if ( key === undefined ) {//不指定key,这个元素所有的都清空。 this.cache[ unlock ] = {}; } else { // $.removeData(document.body , ['age','job','all-name']); if ( jQuery.isArray( key ) ) { //all-name找到allName 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 { // 转驼峰,去空格后在不在 name = camel; name = name in cache ? [ name ] : ( name.match( core_rnotwhite ) || [] ); } } i = name.length; while ( i-- ) { delete cache[ name[ i ] ]; } } }, hasData: function( owner ) { return !jQuery.isEmptyObject( this.cache[ owner[ this.expando ] ] || {} ); }, discard: function( owner ) {//删除1,2,这个整体 if ( owner[ this.expando ] ) { delete this.cache[ owner[ this.expando ] ]; } } }; // These may be used throughout the jQuery core codebase data_user = new Data();//cache就是依附在这个对象上 data_priv = new Data(); //对外提供的接口,通过$.直接调用。这个是方法的定义,准备给jQuery类调用的,里面的实现都是调用Data对象的实现。 jQuery.extend({ acceptData: Data.accepts, hasData: function( elem ) { return data_user.hasData( elem ) || data_priv.hasData( elem ); }, data: function( elem, name, data ) { return data_user.access( elem, name, data ); }, removeData: function( elem, name ) { data_user.remove( elem, name ); }, // TODO: Now that all calls to _data and _removeData have been replaced // with direct calls to data_priv methods, these can be deprecated. //私有的,内部使用,不对外使用 _data: function( elem, name, data ) { return data_priv.access( elem, name, data ); }, _removeData: function( elem, name ) { data_priv.remove( elem, name ); } }); //对外提供的接口,通过jQuery对象调用。设置一组元素时候是设置所有元素,获取元素只是获取第一个。 jQuery.fn.extend({ data: function( key, value ) { var attrs, name, elem = this[ 0 ],//如果是一组div,就只要第一个, i = 0, data = null; // Gets all values if ( key === undefined ) { if ( this.length ) {//$('#div1')找不到得到 data = data_user.get( elem ); //alert($('#div1').get(0).dataset.miaovAll);//h5特性,data-miaov-all="妙味" //对h5特性的处理 if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { attrs = elem.attributes;//元素所有属性数组集合[title='123',class='box',data-miaov-all='苗圩',id='div1'] for ( ; i < attrs.length; i++ ) { name = attrs[ i ].name;//属性的名字 if ( name.indexOf( "data-" ) === 0 ) { //miaov-all转成miaoAll,js中不要出现横杆 name = jQuery.camelCase( name.slice(5) ); //存入cache dataAttr( elem, name, data[ name ] ); } } data_priv.set( elem, "hasDataAttrs", true ); } } return data; } // Sets multiple values$('#div1').data({name:'hello',age:'30'}); if ( typeof key === "object" ) { return this.each(function() { data_user.set( this, key ); }); } return jQuery.access( this, function( value ) { var data, camelKey = jQuery.camelCase( key ); // 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. if ( elem && value === undefined ) { // Attempt to get data from the cache // with the key as-is data = data_user.get( elem, key ); if ( data !== undefined ) { return data; } // Attempt to get data from the cache // with the key camelized data = data_user.get( elem, camelKey ); if ( data !== undefined ) { return data; } // Attempt to "discover" the data in // HTML5 custom data-* attrs data = dataAttr( elem, camelKey, undefined ); if ( data !== undefined ) { return data; } // We tried really hard, but the data doesn't exist. return; } // Set the data... 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 = data_user.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...* data_user.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 ) { data_user.set( this, key, value ); } }); }, null, value, arguments.length > 1, null, true ); }, removeData: function( key ) { return this.each(function() { data_user.remove( this, key ); }); } }); function dataAttr( elem, key, data ) { var name; // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { 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 ) ? JSON.parse( data ) : data; } catch( e ) {} // Make sure we set the data so it isn't changed later data_user.set( elem, key, data ); } else { data = undefined; } } return data; }