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&&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===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

Jquer学习之jQuery(function(){})与(function(){})(jQuery)之间的区别

jquery学习之初始化和获取值

JQUERY插件学习之jQuery UI

jQuery学习之------元素样式的操作

JQuery学习之Ajax应用