一.引子
大家想想,在未使用jQuery或者其他js框架前,只用原生js的时候,怎么存储数据的呢?在刚结束js的时候,我是将每个跟节点有关的属性都使用setAttribute(name,value)保存在节点上。下次取就可以直接getAttribute(name),一个节点使用这种方式保存很多数据,效率肯定不高的。在有时候要对节点保存大量的数据的时候,还有没更好的办法来存储数据呢?下面来看看jQuery是如何做的。
二.原理
jQuery数据的存储原理是:
1)定义了一个对象$.cache 保存所有数据
2)对每一个存储数据的DOM节点都对应一个数字index,这个DOM节点下的所有值都存储在$.cache(index)对象中(这么看是不是觉得$.cache是一个数组? 其实js中数组和对象很像,数组也是对象,对象就是一组属性的集合)。
3)对每一个存储数据的DOM节点都生成一个唯一的index。这个index值保存在节点的expando属性中。
4)expando是什么?就是每一个jQuery框架加载的时候内部生成的一个随机序列。这个序列一个jQuery加载完只有唯一一个。
让图片来更清楚的描述:
jQuery.fn.extend({ //扩展jQuery的对象方法data data: function( key, value ) { var parts, attr, name,data = null; //如果key没有,执行的操作 if ( typeof key === "undefined" ) { if ( this.length ) { /**获取对象数组第一个元素的数据缓存cache * (该对象包含当前元素的所有存储的数据) */ data = jQuery.data( this[0] ); //元素节点 if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { attr = this[0].attributes; for ( var i = 0, l = attr.length; i < l; i++ ) { name = attr[i].name; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.substring(5) ); dataAttr( this[0], name, data[ name ] ); } } jQuery._data( this[0], "parsedAttrs", true ); } } return data; } else if ( typeof key === "object" ) { return this.each(function() { jQuery.data( this, key ); }); } parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; //.data(name)格式,取值 if ( value === undefined ) { data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); // Try to fetch any internally stored data first if ( data === undefined && this.length ) { data = jQuery.data( this[0], key ); data = dataAttr( this[0], key, data ); } return data === undefined && parts[1] ? this.data( parts[0] ) : data; //.data(name, value)格式,设置值 } else { return this.each(function() { var self = jQuery( this ), args = [ parts[0], value ]; //触发事件,如果对节点设置值绑定了事件,在做操作时触发事件 self.triggerHandler( "setData" + parts[1] + "!", args ); jQuery.data( this, key, value ); self.triggerHandler( "changeData" + parts[1] + "!", args ); }); } } });
下面是静态方法:
jQuery.extend({ /** jQuery静态方法,算是data的核心了 * jQuery很多都是这种模式,在静态方法里定义一个核心的处理方法 * 在对象方法中定义用户操作的接口,就是API。 * 如事件:$("#id").click(),$("xx").hover()等等。 * 但最终处理都是调$("#id").on() 到最后是$.event.add()方法 */ data: function( elem, name, data, pvt /* Internal Use Only */ ) { //判断当前节点能不能存储数据 if ( !jQuery.acceptData( elem ) ) { return; } var privateCache, thisCache, ret, internalKey = jQuery.expando, getByName = typeof name === "string", /**判断元素是否是DOM对象,如果是DOM对象才使用全局cache,普通js对象,直接增加属性即可. * DOM对象可以直接增加属性,为什么这个非得用全局对象呢? * 这主要是解决IE6-7浏览器垃圾回收对于js创建的DOM对象的属性不能回收 * 验证方法:(使用sIEve 可看到有内存泄露) * function A() { * var a = document.createElement("div"); * a["b"] = function(){}; * document.getElementById("test").appendChild(a); * a.parentNode.removeChild(a); * } * A(); */ isNode = elem.nodeType, cache = isNode ? jQuery.cache : elem, //获取index id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, isEvents = name === "events"; // 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] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { return; } if ( !id ) { //为每一个存储数据在全局cache中的DOM节点分配一个唯一index if ( isNode ) { elem[ internalKey ] = id = ++jQuery.uuid; } else { id = internalKey; } } if ( !cache[ id ] ) { cache[ id ] = {}; // Avoids exposing jQuery metadata on plain JS objects when the object // is serialized using JSON.stringify if ( !isNode ) { cache[ id ].toJSON = jQuery.noop; } } //如果name是对象或函数,继承里面所有方法.注意:这边为什么判断pvt ? 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 ); } } privateCache = thisCache = cache[ id ]; //要知道pvt是jQuery自己内部使用的,将保存用户和jQuery内部数据的保存分开了。 //将我们设置的name/value值 //放在了cache[id].data对象中,而jQuery内部数据直接放在了cache[id]中 //好处不言而喻,将用户和jQuery框架的数据分开保存,避免了名称冲突 //1.7版本之后才区分开.上面例子已经说了 if ( !pvt ) { if ( !thisCache.data ) { thisCache.data = {}; } thisCache = thisCache.data; } //存储数据 if ( data !== undefined ) { thisCache[ jQuery.camelCase( name ) ] = data; } // Users should not attempt to inspect the internal events object using jQuery.data, // it is undocumented and subject to change. But does anyone listen? No. if ( isEvents && !thisCache[ name ] ) { return privateCache.events; } if ( getByName ) { ret = thisCache[ name ]; if ( ret == null ) { ret = thisCache[ jQuery.camelCase( name ) ]; } } else { ret = thisCache; } return ret; } });
本章结束,有不对和不准确的地方望大家指正。有疑问欢迎留言。