首页 > 代码库 > jQuery源代码学习之六——jQuery数据缓存Data

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=http://www.mamicode.com/==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&&copy&&(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=http://www.mamicode.com/==false){                            break;                       }                    }                }else{                    for(i in object){                        value=callback.call(object[i],args);                        if(value=http://www.mamicode.com/==false){                            break;                        }                    }                }            }else{                if(isArray){                    for(i=0;i<length;i++){                        value=callback.call(object[i],i,object[i]);                        if(value=http://www.mamicode.com/==false){                            break;                        }                    }                }else{                    for(i in object){                        value=callback.call(object[i],i,object[i]);                        if(value=http://www.mamicode.com/==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!=null){                //1,没有length属性,或者具有length属性,但是是以下几种情况的                //2.如果array是string 的length表示字符串的长度                //3.如果array是函数,其length代表函数生命时的参数个数                //4,如果array是window对象,属性Length返回窗口中的框架(frame,iframe)个数                if(array.length==null|| type==string || type==function ||type==regexp||jQuery.isWindow(array)){                    core_push.call(ret,array);                }else{//否则说明是类数组对象                    jQuery.merge(ret,array);                }            }            return ret;        },                  inArray:function(elem,array,i){            return array==null?-1:core_indexOf.call(array,elem,i);        },        //用于合并两个数组的元素到第一个数组中        //事实上,jquery源代码中第一个参数可以是数组或者类数组对象,第二个参数可以是数组、类数组对象或任何含有连续整型属性的对象        //第一个参数是数组,最后返回数组;第一个参数是类数组,则返回类数组        merge:function(first,second){            var l=second.length,                i=first.length,                j;            if(typeof l==number){                for(j=0;j<l;j++){                    first[i++]=second[j];                }               }else{                while(second[j]!=undefined){                    first[i++]=second[j++];                }            }                        first.length=i;            return first;        },        //用于查找数组中满足过滤函数的元素,形成新的数组之后返回,原数组不受影响        //如果inv未传入或者是false,元素只有在过滤函数返回true时,才会被保存在最终的结果数组中        //如果参数inv是true,则恰好相反        grep:function(elems,callback,inv){            var i,                ret=[],                length=elems.length,                retVal;            inv=!!inv;            for(i=0;i<length;i++){                retVal=!!callback.call(elems[i],i);                if(retVal!==inv){                    ret.push(elems[i]);                }            }            return ret;                     },        //用于对数组中每个元素执行callback操作,并将结果形成新的数组返回        //参数arg仅仅是jQuery内部使用        map:function(elems,callback,arg){            var ret=[],                retVal,                i,                length=elems.length,                isArray=isArrayLike(elems);            if(isArray){                for(i=0;i<length;i++){                    retVal=callback(elems[i],i,arg);//注意不是callback.call                    if(retVal!=null){                        ret.push(retVal);                    }                }            }else{                for(i in elems){                    retVal=callback(elems[i],i,arg);                    if(retVal!=null){                        ret.push(retVal);                    }                }            }            //保证最终返回的是一维数组            return core_concat.call([],ret);        },        guid:1,        //该方法用于更改函数的执行上下文        //源代码中有两种传参形式,这里仅考虑最常见的一种        proxy:function(fn,context){            if(!jQuery.isFunction(fn)){                return undefined;            }            var args=core_slice.call(arguments,2);                proxy=function(){                    return fn.call(context||this,core_concat.call(args,core_slice.call(arguments)));                };            proxy.guid=fn.guid=fn.guid||jQuery.guid++;            return proxy;        },        //用一个方法同时实现get和set操作        //如何设置或者获取由回调函数fn确定        //这个方法的实现等用到的时候结合来看        access: function( elems, fn, key, value, chainable, emptyGet, raw ){                    },        now:Date.now,        //该方法用于交换css样式,在support模块较多用到        //要交换的样式由参数options传递        swap: function( elem, options, callback, args ){            var name,ret,                old={};            for(name in options){                old[name]=elem.style[name];                elem.style[name]=options[name];            }            ret=callback.call(elem,args||[]);            for(name in options){                elem.style[name]=old[name];            }            return ret;        },            });    //目前,js中typeof的返回值有六种:"number," "string," "boolean," "object," "function," 和 "undefined."    //通过object.prototype.toString/或者{}.toString 返回值有九种:Boolean Number String Function Array Date RegExp Object Error,其中的Array,Date,RegExp,Object,Error都属于Object类型,在有些浏览器中typeof 正则会返回function    jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(i,name){        class2type["[object "+name+"]"]=name.toLowerCase();    });    //console.log(class2type,class2type);    rootjQuery=jQuery(document);    /****接下来这一部分,在jQuery的源代码中,本来是Sizzle,这里暂且略过***/   var optionsCache={};   function createOptions(options){        var object=optionsCache[options]={};        //\S+,匹配非空格字符        //正则表达式如果没有g,仅匹配第一个匹配项        jQuery.each(options.match(/\S+/g),function(i,item){            object[item]=true;        });        return object;   }   //参数options可以是字符串或者是对象形式,可选属性/字符串组合有   //once:回调函数列表只能执行一次   //memory:fire调用之后,再次add将立即触发   //unique:同一个函数不能被重复添加到回调函数列表中   //stopOnFlase:当某一个函数返回false时,为true的时候,回调函数列表的执行终止   jQuery.Callbacks=function(options){        options=typeof options===string?optionsCache[options]||createOptions(options):options||[];        var list=[],//用于存储回调函数列表            firingStart,            once=options.once,            memory,//初始值为undefined,只有在memory模式下,调用fire后才会被赋值,以备在add中再次调用                       fired=false,//指示是否fire过            firingIndex,//指向要执行的下一个回调函数的下标            add=function(arg){                                var type;                jQuery.each(arg,function(i,item){                    type=jQuery.type(item);                    if(type===function&&!(options.unique&&self.has(item))){                        list.push(item);                    }else if(type ===array){                        add(item);                    }                });                            },            fire=function(data){                fired=true;                memory=options.memory&&data;                  firingIndex=firingStart||0;                             firingStart=0;//在memory模式下,add的时候firingStart可能会被置为其他值,这里将其还原,以备下次调用fire的时候从头开始执行                var length;                                    if(!list){                    return;                }                          for(length=list.length;firingIndex<length;firingIndex++){                    if(list[firingIndex].apply(data[0],data[1])===false&&options.stopOnFalse){                        break;                    }                }                // if(once){                //     if(memory){//如果通知是once和memory模式,那么在add的时候可以进行再次触发                //         list=[];                //     }else{//否则直接禁用                //         self.disable();                //     }                // }            },            self={                add:function(){                    if(list){                        var start=list.length;                        add(arguments);                        //如果是memory模式下的add,会导致立即触发                        if(memory){//memory的初始值为undefined,memory模式下调用一次fire才会被赋值,因此第一次调用add的时候不会走下面                            firingStart=start;                            fire(memory);                        }                    }                    return this;                },                remove:function(){                    if(list){                        var i;                        jQuery.each(arguments,function(i,item){                            //jQuery.inArray(item,list,i),返回item在list中的下表,从第i位向后数,包括第i为                            while((i=jQuery.inArray(item,list,i))>-1){                                                                     list.splice(i,1);//删除上的数值                                                            }                        });                    }                    return this;                },                               //fn有值的时候,代表判断回调函数列表是否存在函数fn                //没有参数fn的时候,代表判断回调函数列表是否为空                has:function(fn){                    return fn?jQuery.inArray(fn,list)>-1:!!(list&&list.length);                },                                empty:function(){                    if(list){                        list=[];                    }                    return this;                },                disable:function(){                    //list就不用说了,list置为undefined之后,几乎所有的方法都不能调用                    //memory恢复初始值undefined                    list=memory=undefined;                    return this;                },                disabled:function(){                    return !list;                },                fireWith:function(context,args){                    if(list&&!(once&&fired)){                        args=args||[];//主要是为了处理args为undefined的情况                        args=[context,args.slice?args.slice():args];                        fire(args);                                            }                    return this;                },                fire:function(){                    self.fireWith(this,arguments);                    return this;                },                fired:function(){                    return !!fired;                },                //自己加的函数,供调试用                getList:function(){                    return list;                }            };            return self;   };   //实现异步队列Defered,When   //异步队列内部维护了三个回调函数列表,分别是成功,失败,消息   jQuery.extend({        //func参数仅内部使用,func的调用者是jQuery.Deferred的返回值,参数也是        Deferred:function(func){            var doneList=jQuery.Callbacks(once memory),                failList=jQuery.Callbacks(once memory),                progressList=jQuery.Callbacks(memory),                state=pending,                list={                    resolve:doneList,                    reject:failList,                    notify:progressList                },                promise={                    done:doneList.add,                    fail:failList.add,                    progress:progressList.add,                                        state:function(){                        return state;                    },                    //同时添加成功,失败,消息回调函数                    then:function(doneCallback,failCallback,progressCallback){                        deferred.done(doneCallback).fail(failCallback).progress(progressCallback);                    },                    //成功,失败时,添加同一个处理函数                    always:function(){                        deferred.done(arguments).fail(arguments);                    },                    //说实话,能看懂这个源代码,但搞不太懂这个pipe是干嘛用的                    //实际使用中调用的地方也不多                    //不过其源代码有不少知识点值得学习                    pipe:function(fnDone,fnFail,fnProgress){                        //这里的newDefer,就是调用jQuery.Deferred(function(newDeferred))返回的异步队列对象,由这部分代码最终的func.apply(deferred,deferred)决定;                        return jQuery.Deferred(function(newDefer){                            jQuery.each({                                done:[fnDone,resolve],                                fail:[fnFail,reject],                                progress:[fnProgress,notify]                                                          },function(handler,data){                                //注意这三个局部变量定义的位置,只能定义在该闭包中,如果定义在jQuery.Deferred得到的只是函数最后的值,如果没有传递fnProgress,就会报出undefined的错误                                var action=data[1],                                    fn=data[0],                                    returned;                                if(jQuery.isFunction(fn)){                                    //通过done,fail,progress添加的方法,只有在对应的回调函数队列fire的时候才会触发                                    deferred[handler](function(){                                        //这里的this,arguments是调用fire/fireWith时候传递                                        //这里的this可以通过fireWith中指定context,arguments也是fire/fireWith的时候传递的参数                                                                                returned=fn.apply(this,arguments);                                        //如果函数的返回值依旧是一个异步队列,则将jQuery.pipe返回的异步队列的成功,失败,消息回调添加到返回的retuned对应的回调列表中                                        if(returned&&jQuery.isFunction(returned.promise)){                                            returned.promise().then(newDefer.resolve,newDefer.reject,newDefer.notify);                                        }else{                                            //如果函数返回值不是异步队列,则jQuery.pipe()返回的异步队列对应状态的方法立即触发                                            newDefer[action+With](this===deferred?newDefer:this,[returned]);                                        }                                    });                                }else{                                    deferred[handler](newDefer[action]);                                }                            });                        }).promise();                    },                    //注意promise()和promise({})这两种写法是完全不同的,前者返回异步对象的只读版本,后者返回一个副本                    promise:function(obj){                        return obj==null?promise:jQuery.extend(obj,promise);                    },                  },                deferred=promise.promise({}),                key;                //为deferred添加状态改变的相关函数,与fire,fireWith相对应            for(key in list){                deferred[key]=list[key].fire;                deferred[key+With]=list[key].fireWith;            }            deferred.done(function(){                state=resolved;            },failList.disable,progressList.disable)            .fail(function(){                state=rejected;            },doneList.disable,progressList.disable);                            if(func){                //这句话决定了,通过jQuery.Deferred(func)调用的时候,func的context和参数                func.call(deferred,deferred);            }            return deferred;        },        When:function(firstParam){            var resolveArgs=core_slice.call(arguments,0),//用来存放成功参数                length=resolveArgs.length,                count=length,//维护一个计数器                progressArgs=new Array(length),//用来存放消息参数                i=0,                //只有当在只有一个参数,并且该参数是延迟对象的情况下,主延迟对象等于该第一个参数,否则新建一个主延迟对象                deferred=length<=1&&firstParam&&jQuery.isFunction(firstParam.promise)?firstParam:jQuery.Deferred(),                promise=deferred.promise();            if(length>1){                for(;i<length;i++){                    if(resolveArgs[i]&&jQuery.isFunction(resolveArgs[i].promise)){                        resolveArgs[i].then(resolveFunc(i),deferred.reject,progressFunc(i));                    }else{                        count--;                    }                    if(!count){                        deferred.resolveWith(deferred,resolveArgs);                    }                }                            }else if(deferred!==firstParam){//说明只有一个或0个参数,若有一个,该参数还不是延迟对象                //此时立即触发                deferred.resolveWith(deferred,length?[firstParam]:[]);            }            //为了将参数i的值传递,这里采用闭包            function resolveFunc(i){                //回调函数的参数(即返回函数中的value/arguments)是由fire/fireWith的时候进行参数指定                return function(value){                    resolveArgs[i]=arguments.length>1?core_slice.call(arguments):value;                    //每一次参数延迟对象的resolve触发,都令count的值减去一                    if(!--count){                        //如果计算器变为0,那么主延迟对象的resolve方法触发                        deferred.resolveWith(deferred,resolveArgs);                    }                }            }            function progressFunc(i){                return function(value){                    progressArgs[i]=arguments.length>1?core_slice.call(arguments):value;                                        deferred.notifyWith(promise,progressArgs);                }            }            return promise;        }       });/*********数据缓存模块****************************************///数据缓存模块的整体思路//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=http://www.mamicode.com/==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){    }        window.jQuery=window.$=jQuery; })(window); 
myJquery.js(10.30)

 

jQuery源代码学习之六——jQuery数据缓存Data