jQuery源代码学习之六——jQuery数据缓存Data
Posted 初学者学习笔记
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jQuery源代码学习之六——jQuery数据缓存Data相关的知识,希望对你有一定的参考价值。
一、jQuery数据缓存基本原理
jQuery数据缓存就两个全局Data对象,data_user以及data_priv;
这两个对象分别用于缓存用户自定义数据和内部数据;
以data_user为例,所有用户自定义数据都被保存在这个对象的cache属性下,cache在此姑且称之为自定义数据缓存;
自定义数据缓存和DOM元素/javascript对象通过id建立关联,id的查找通过DOM元素/javascript元素下挂载的expando属性获得
话不多说,直接上代码。相关思路在代码注释中都有讲解
二、jQuery数据缓存部分的源代码
/*********数据缓存模块****************************************/ //数据缓存模块的整体思路 //2.0.3版本的jQuery较之于1.7.3版本,使用面向对象的写法重构了数据缓存Data模块 //数据缓存模块的整体依据是: //data_user和data_priv在一次运行期间只有对应的唯一对象,所有DOM元素的缓存都基于这两个实例对象完成 //data_user与data_priv这两个Data实例有各自的缓存对象属性cache,分别用于存储用户自定义数据和内部数据 //以data_user为例,在向对应的data_user对应的缓存对象cache中保存数据时,会为每个DOM元素分配一个唯一的id,该id作为该DOM元素的附加属性 //该唯一id(初始值为0,之后一次加1)会附加到DOM元素上,对应的DOM元素的属性名是data_user.expando,其对应的属性值就是id //同时,会把该id作为属性名添加到data_user的缓存对象属性cache中,对应的属性值是一个都object对象,该对象称为DOM元素的数据缓存对象,其中存储着属性名和属性值的映射 //这样,通过分配唯一的id把DOM元素和该DOM元素的数据缓存对象关联起来 //data_priv与之类似 var data_priv,data_user, rbrace=/^(?:\\{\\s\\S*\\}|\\[\\s\\S*\\])$/,//匹配json字符串格式,诸如{},或者[],不用.*进行匹配的原因是.不能匹配换行符 rmultiDash=/([A-Z])/g;//匹配任意的大写字母 function Data(){ //jQuery.expando是jQuery的静态属性,对于jQuery的每次加载运行期间时唯一的 //Math.random生成一个0-1之间的随机数 this.expando=jQuery.expando+Math.random(); this.cache={}; //这里采用访问器属性的写法 //常用的写法是Object.defineProperty(对象,对象属性,{[[get]],[[set]],[[configurable]],}) //这句话的目的,this.cache中的0属性是个只读属性 Object.defineProperty(this.cache,0,{ get:function(){ return {}; } }); } //下面可以看到,只有当accepts为false的时候,返回的id为0 Data.uid=1; Data.accepts=function(owner){ //只有DOM元素,document元素,以及普通的js对象可以操作数据缓存 return owner.nodeType?owner.nodeType===1||owner.nodeType===9:true; }; Data.prototype={ //获取(设置)owner对应的id,如果没有,则为其this.expando对应的属性,值为id,并未其在this.expando中创建缓存对象 key:function(owner){ if(!Data.accepts(owner)){ return 0; } var expando=this.expando, id=owner[expando]; if(!id){ id=Data.uid++; //为owner定义expando属性,为了保证该属性不可遍历且只读,使用访问器属性进行定义 //defineProperty一次只定义一个属性,接受三个参数,对象,属性名,属性描述对象 //defineProperties可以通过描述符一次定义多个属性,接受两个参数 //具体用法可以参照讲解http://www.tuicool.com/articles/ju26riE Object.defineProperty(owner,expando,{ value:id, }); } if(!this.cache[id]){ this.cache[id]={}; } return id; }, //为DOM元素对应的缓存设置数据 //data参数可以是字符串,也可以是对象,当data是数组的时候,value可以不赋值 set:function(owner,data,value){ var id=this.key(owner), //该DOM元素对应的缓存对象 cache=this.cache[id], key; if(typeof data===\'string\'){ cache[data]=value; }else{ for(key in data){ cache[key]=data[key]; } } return cache; }, //获取DOM元素owner对应缓存中属性key的值 //如果参数key不赋值,则代表去除owner对应的对象缓存 get:function(owner,key){ var id=owner[this.expando], cache; if(!id){ return undefined; } cache=this.cache[id]; return key?cache[key]:cache; }, //设置或获取 access:function(owner,key,value){ var tmp; if(!key||((key&&typeof key===\'string\') &&!value)){//说明是获取 //先尝试key本身,不行的话尝试转驼峰 tmp=this.get(owner,key); return tmp? tmp: this.get(owner,jQuery.camelCase(key)); } //否则说明是设置 this.set(owner,key ,value); return value ? value : key; }, //如果没有传入参数key,则移除DOM元素或者javascript元素关联的所有数据 //如果传入了参数key,则移除关联的指定名称的数据 //如果key是数组或者空格分割的多个数据名,则一次可以删除多个数据,删除的时候还需要尝试camel转换之后的形式 remove:function(owner,key){ var i,camel,length, id=this.key(owner), cache=this.cache[id]; if(!key){ this.cache[id]={}; }else{ //可能是数组,可能是字符串,该字符串还可能使用空格分开 if(typeof key ===\'string\'){ key=key.match(rnotwhite);//转换为数组形式 } key=key.concat(jQuery.map(key,jQuery.camelCase)); for(i=0,length=key.length;i<length;i++){ delete cache[key[i]]; } } }, //返回owner对应的缓存对象是否有值 hasData:function(owner){ return !jQuery.isEmptyObject(this.cache[owner[this.expando]]||{}); }, //删除Owner对应的缓存对象(注意不是讲缓存对象置为空数组) discard:function(owner){ if(owner[this.expando]){ delete this.cache[owner[this.expando]]; } } }; data_user=new Data(); data_priv=new 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); }, _data:function(elem,name,data){ return data_priv.access(elem,name,data); }, _removeData:function(elem,name){ data_priv.remove(elem,name); }, //下面这两个get方法在jquery源代码中没有,这里加上,便于测试 get:function(elem,key){ return data_user.get(elem,key); }, _get:function(elem,key){ return data_priv.get(elem,key); } }); //这部分操作的都是用户自定义数据 jQuery.fn.extend({ data:function(key,value){ //在这一步中,jQuery的源代码考虑了 jQuery.each(this,function(){ jQuery.data(this,key,value); //console.log(jQuery.get(this)); //console.log($(this).getData()); }); }, removeData:function(key){ //别忘了,jquery对象是类数组 jQuery.each(this,function(){ // data_user.remove(this,key); jQuery.removeData(this,key); }); }, //下面的getData方法也是为了测试方便加上的,在jQuery源代码中没有 getData:function(key){ //下面这种写法不对,注意结合jQuery.each源代码看就知道,get到的值并没有返回 // jQuery.each(this,function(){ // jQuery.get(this,key); // }); //根据jQuery的思路,获取的时候,获得的总是首个 return jQuery.get(this[0],key); } }); //jQuery的源代码中用于处理html5中的data属性,这里暂不考虑 function dataAttr(elem,key,data){ }
截止目前的,myJquery.js全部代码(16年10月30日)
(function(window,undefined){ var rootjQuery, core_version=\'2.0.3\', idExpr=/^#([\\w\\-]*)$/, //下面两个正则用于转驼峰 rmsPrefix = /^-ms-/, rdashAlpha = /-([\\da-z])/gi, rnotwhite = /\\S+/g,//匹配非空白字符 class2type={}, core_deletedIds=[], core_version=\'2.0.3\', _jQuery=window.jQuery, _$=window.$, core_toString=class2type.toString, core_hasOwn=class2type.hasOwnProperty, core_trim=core_version.trim, core_indexOf=core_deletedIds.indexOf, core_push=core_deletedIds.push, core_concat=core_deletedIds.concat, core_slice=core_deletedIds.slice, //用于jQuery.camelCase转驼峰函数中 //当replace函数只有一个匹配项时,第二个参数可以是一个函数 //如果repalce中的正则没有捕获组,会向这个函数传递三个参数:模式的匹配项,模式匹配项在字符串中的位置,原始字符串 //如果replace中的正则有捕获组,也会向这个函数传递三个参数,模式的匹配项,捕获组的匹配项,模式匹配项在字符串中的位置 fcamelCase=function(all,letter){ return letter.toUpperCase(); }, jQuery=function(selector,context){ return new jQuery.fn.init(selector,context,rootjQuery); }; //jQuery相关实例方法和属性 jQuery.fn=jQuery.prototype={ jQuery:core_version,//其实就是版本字符串2.0.3 constructor:jQuery,//还原constructor指向 selector:\'\',//含有连续的整型属性、length属性、context属性,selector属性(在jQuery.fn.init中设置),preObject属性(在pushStack中设置) length:0, init:function(selector,context,rootjQuery){ var match,elem; //selector是选择器表达式 if(!selector){ return this; } if(typeof selector ===\'string\'){ match=idExpr.exec(selector); if(match&&!context){ elem=document.getElementById(match[1]); if(elem&&elem.parentNode){ this[0]=elem; this.length=1; } this.selector=selector; this.context=document; return this; }else{ //说明是复杂的选择器表达式,这里暂不考虑 } } //处理selector是DOM元素的情形 if(selector&&selector.nodeType){ this[0]=selector; this.length=1; this.context=selector; return this; } //处理selector是函数的情形 if(jQuery.isFunction(selector)){ return rootjQuery.ready( selector ); } //处理selector是jQuery对象的情形 if(selector.selector){ this.selector=selector.selector; this.context=selector.context; } //处理其他情形 return jQuery.makeArray(selector,this); }, //将jQuery类数组对象转换为数组 toArray:function(){ return core_slice.call(this); }, //如果传递了参数num,代表获取下标num的DOM元素(num可以为负数) //如果没有传递num,则将jQuery对象转换为数组后整体返回 get:function(num){ if(num==null){//注意这里不能用!num,因为num可以为0 return this.toArray(); } return num<0?this[num+this.length]:this[num]; }, //入栈 pushStack:function(elems){ var ret=jQuery.merge(this.constructor(),elems); ret.prevObject=this; ret.context=this.context; return ret; }, //遍历jQuery对象 each:function(callback,args){ //在静态方法已经指定了callback的执行上下文 return jQuery.each(this,callback,args); }, //加载完成事件方法,这里暂不考虑 ready:function(fn){}, slice:function(){ //注意apply和call的区别 return this.pushStack(core_slice.apply(this,arguments)); }, first:function(){ return this.get(0); }, last:function(){ return this.get(-1); }, eq:function(i){ var length=this.length, j=+i+(i<0?length:0); return this.pushStack(j>=0&&j<length?[this[j]]:[]); }, map:function(callback){ //这种写法不能指定callback的执行环境,因为在静态方法jQuery.map并没有指定callback的执行上下文 // return this.pushStack(jQuery.map(this,callback)); return this.pushStack(jQuery.map(this,function(elem,i){ return callback.call(elem,i,elem); })); }, //与pushStack方法相对应,返回栈的上一级 end:function(){ return this.prevObject||this.constructor(); }, push:core_push, sort:[].sort, splice:[].splice, }; jQuery.fn.init.prototype=jQuery.fn; //可接受的参数类型如下:jQuery.extend([deep],target,object1,[objectN]) jQuery.extend=jQuery.fn.extend=function(){ var target=arguments[0]||{},//指向目标对象 deep=false,//是否进行深度复制 i=1,//表示源对象的起始下标 length=arguments.length,//表示参数个数; options,name,src,copy,copyIsArray;//options指向某个源对象,name指向源对象的某个属性名,src目标对象某个属性的原始值,copy某个源对象的某个属性的值,copyIsArray指示变量copy是否为数组 //首先进行参数修正 if(typeof target===\'boolean\'){ deep=target; target=arguments[1]||{}; i=2; } //此时target就是jQuery或jQuery.fn if(i===length){ target=this; i--; } //处理target是字符串或者其他情形,这在深度复制中可能出现 // if(typeof target!==\'object\'||!jQuery.isFunction(target)){ // target={}; // } for(i;i<length;i++){ options=arguments[i]; for(name in options){ src=target[name]; copy=options[name]; if(deep&©&&(jQuery.isPlainObject(object)||(copyIsArray=jQuery.isArray(object)))){ if(copyIsArray){ copyIsArray=false; clone=src&&jQuery.isArray(src)?src:[]; }else{ clone=src&&jQuery.isPlainObject(src)?src:{}; } target[name]=jQuery.extend(deep,clone,copy); }else{ target[name]=copy; } } } return target; }; //检查是否是数组或者类数组 function isArrayLike(obj){ var length=obj.length, type=jQuery.type(obj); if(obj&&jQuery.isWindow(obj)){ return false; } if(obj.nodeType===1&&length){ return true; } if(type===\'array\'){ return true; } if(typeof length===\'number\'&&(length==0||(length>0&&(length-1) in obj))){ return true; } return false; } jQuery.extend({ //一堆静态方法和属性 expando:\'jQuery\'+(core_version+Math.random()).replace(/\\D/g,\'\'), // 该函数用于释放jQuery对于全局变量$的控制权,可选的参数deep代表是否释放对全局变量jQuery的控制权 noConflict:function(deep){ if(window.$===jQuery){ window.$=_$; } if(deep&&window.jQuery===jQuery){ window.jQuery=_jQuery; } return jQuery; }, /********isReady,readyWait,holdReay,ready与加载事件有关,暂且略过***********/ isReady:false, readyWait:1, holdReady:function(hold){}, ready:function(){}, /*******/ /****下面是一系列类型检测的静态方法*******/ isFunction:function(obj){ //如果使用typeof,在有些浏览器中,正则也会返回function,因此这里采用jQuery处理后的方法,jQuery.type return jQuery.type(obj)===\'function\'; }, isArray:Array.isArray, isWindow:function(obj){ return obj!==null&&obj===obj.window; }, //判断obj是否为数字或者数字类型的字符串,并且是有效数字 isNumeric:function(obj){ return !isNaN(parseFloat(obj))&&isFinite(obj); }, type:function(obj){ if(obj===null){ return String(null); } //Date,Array等类型typeof都会返回object,function、正则(部分浏览器)中 typeof都会返回function if(typeof obj===\'object\'||typeof obj===\'function\'){ return class2type[core_toString.call(obj)]||\'object\'; } return typeof obj; }, //判断是否为以下两种情况:1,对象字面量;2,通过new Object()创建 isPlainObject:function(obj){ if(jQuery.type(obj)!==\'object\'||obj.nodeType||jQuery.isWindow(obj)){ return false; } //如果是纯粹的对象,那么obj一定有constructor属性,并且方法hasOwnPropertyOf一定就在构造函数本身的原型中,而不用通过原型链查找得到 if(obj.constructor&&!core_hasOwn.call(obj.constructor.prototype,\'isPrototypeOf\')){ return false; } return true; }, //检查是否是空对象 isEmptyObject:function(obj){ for(var name in obj){ return false; } return true; }, /******类型检测静态方法结束********/ error:function(msg){ throw new Error(msg); }, //将html字符串转换为html DOM结构, parseHTML: function( data, context, keepScripts ){ }, parseJSON:JSON.parse, parseXML:function(data){ var xml, tmp; if ( !data || typeof data !== "string" ) { return null; } // Support: IE9 try { tmp = new DOMParser(); xml = tmp.parseFromString( data , "text/xml" ); } catch ( e ) { xml = undefined; } if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { jQuery.error( "Invalid XML: " + data ); } return xml; }, noop:function(){}, //用于在全局作用域执行javascript代码,这里暂略 globalEval:function(data){}, //转换连字符字符串为驼峰类型 camelCase:function(string){ return string.replace(rmsPrefix,\'ms-\').replace(rdashAlpha,fcamelCase); }, //判断elem的nodeName是否=name nodeName:function(elem,name){ return elem.nodeName&&elem.nodeName.toLowerCase()==name.toLowerCase(); }, //jQuery遍历方法,其中args是传递给回调callback的参数,仅供jQuery内部使用;外部调用该方法时,回调的参数默认为数组下标/对象key,对应数组值/对象value each:function(object,callback,args){ var i, value, length=object.length, isArray=isArrayLike(object); if(args){//说明是内部调用 if(isArray){ for(i=0;i<length;i++){ value= callback.call(object[i],args); if(value===false){ break; } } }else{ for(i in object){ value=callback.call(object[i],args); if(value===false){ break; } } } }else{ if(isArray){ for(i=0;i<length;i++){ value=callback.call(object[i],i,object[i]); if(value===false){ break; } } }else{ for(i in object){ value=callback.call(object[i],i,object[i]); if(value===false){ break; } } } } return object; }, trim:function(str){ return str==null?\'\':core_trim.call(str); }, //将一个类数组对象转换为真正的对象 //results参数仅供jquery内部使用,此时在该参数的基础上添加元素 makeArray:function(array,results){ var ret=results||[], type=jQuery.type(array); //undefined,null都会==null if(array!=nulljQuery源代码学习之五——jQuery.when