jQuery-1.9.1源码分析系列(四) 缓存系统

  先前在分析Sizzle的时候分析到Sizzle有自己的缓存机制点击这里查看。不过Sizzle的缓存只是对内使用的(内部自己存,自己取)。接下来分析jQuery可以对外使用的缓存(可存可取)。

  首先需要明白jQuery缓存需要解决什么问题,实现它的意义?

  jQuery缓存要解决的是在往DOM节点添加数据(这些数据往往和该DOM节点紧密相关),但是给DOM添加数据或自定义属性可能起内存泄漏(DOM发生缓存泄漏导致DOM的数据没法被删除,那么添加到DOM的数据也无法被回收。久而久之,会出现一大片内存得不到释放。),所以应该要尽量避免这样做。更好的解决方法是使用一种低耦合的方式让DOM和缓存数据能够联系起来。

  jQuery怎么做?

  jQuery定义了一个属性cache = {}来保存所有的缓存数据。在DOM节点上添加一个expando的值(expando的值等于”jQuery”+当前时间)为属性名称的属性,这个属性的值id = dom[jQuery.expando]用来查找jQuery.cache上对应的缓存数据:jQuery.cache[id].data 即为DOM对应的缓存数据。为了保证了id 的全局唯一性,这个id使用jQuery.guid自增。例如

$("#demo").data("name","chua");
$("#demo").data("name");//"chua"

  jQuery底层接口还做了拓展,不仅仅能缓存在DOM节点的数据,还可以缓存非DOM节点的对象的数据。这种方式最终数据是附加到了对象obj自己身上,而没有使用全局缓存jQuery.cache。该方式也会在对象obj上添加一个expando的值为属性名称的属性,缓存数据保存在obj[jQuery.expando].data上。不过这个一般不推荐外部使用,因为调用的是更底层的api: jQuery.data,而非$(...).data。例子:

var obj = {};
$.data(obj,'name','chua');
$.data(obj,'name');//"chua"

  

  接下来我们开始解析源码。

jQuery.fn.extend({
    data: function( key, value ) {…    },//内部使用基础api:jQuery.data来实现,可以使用参数(key,value),也可以使用参数(obj)
    removeData: function( key ) {…}//内部使用基础api: jQuery.removeData来实现
});

所以我们主要看底层基础API部分

jQuery.extend({
    cache: {},
    // 每个jQuery拷贝都有一个其唯一的标志。比如你的页面有两个iframe且每个iframe都用到的jQuery。name你的两个iframe就有两份jQuery拷贝。
    expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
    // 下面的元素将抛出不可捕获的异常,如果你尝试给他们添加expando属性
    //主要用在acceptData函数中确定元素是否可以添加expando属性
    noData: {
        "embed": true,
        // Ban all objects except for Flash (which handle expandos)
        "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
        "applet": true
    },
    hasData: function( elem ) {…},
  data: function( elem, name, data ) {return internalData( elem, name, data );},
  removeData: function( elem, name ) {return internalRemoveData( elem, name );},
    // 内部使用
    _data: function( elem, name, data ) {return internalData( elem, name, data, true );},
    _removeData: function( elem, name ) {return internalRemoveData( elem, name, true );},
    // 用来确定DOM节点是否能够添加expando 数据
    acceptData: function( elem ) {
        // non-element不能添加数据
        if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
            return false;
        }
        var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
        // nodes accept data unless otherwise specified; rejection can be conditional
        return !noData || noData !== true && elem.getAttribute("classid") === noData;
    }
});

  所以归根结底缓存数据和取出缓存数据是使用内部函数internalData( elem, name, data, pvt /* Internal Use Only */ )

  

a. 存取缓存的内部函数function internalData( elem, name, data, pvt /* Internal Use Only */ )


   internalData的处理流程为:

  1.判断如果元素不支持添加属性的直接返回。取出缓存容器和缓存中使用的ID备用。这里面粉两种情况,如果传递的对象elem是DOM对象,则缓存取全局缓存容器jQuery.cache,id取elem[jQuery.expando](如果没有的话使用jQuery.guid自增值设置一个);如果elem不是DOM对象,则缓存容器取对象本身elem,id取jQuery.expando

var thisCache, ret,
        //获取每份jquery拷贝的标志
        internalKey = jQuery.expando,
        getByName = typeof name === "string",

        //我们将DOM节点和JS对象区分开来,因为IE6-7不能跨越DOM-JS界限正确回收对象引用
        //所有DOM节点上没有附加数据,而是存放再来全局jQuery缓存jQuery.cache中
        isNode = elem.nodeType,

        //只有DOM节点需要全局jQuery缓存;js对象数据直接附加到对象上使得垃圾回收机制能够自动回收
        cache = isNode ? jQuery.cache : elem,

        //假如JS对象的缓存已经存在,则拷贝标志作为一个访问ID。
        //如果是DOM对象,则拷贝标志作为一个属性附加到dom上,这个属性的值作为访问全局缓存的一个路径ID
        id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;

    //预先处理要获取数据,但是缓存中没有数据的情况。这种情况应当直接返回
    if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
        return;
    }

    if ( !id ) {
        //只有dom节点的每个元素都需要唯一的ID,直到他们的数据在全局缓存中清除
        if ( isNode ) {
            //启用删除的id中最后一个id,或是对象的全局GUID统计
            elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++;
        } else {
            id = internalKey;
        }
    }
View Code

  2.取出的缓存容器cache[ id ]如果不存在就初始化为空对象

        //如果缓存中没有数据
    if ( !cache[ id ] ) {
        //初始化对象
        cache[ id ] = {};

        //避免在对象使用JSON.stringify序列化的时候暴露jQuery的元数据给普通对象
        if ( !isNode ) {
            cache[ id ].toJSON = jQuery.noop;
        }
    }
    
View Code

  3.cache[ id ]并非真正的缓存数据,真正的缓存数据保存在cache[ id ].data上。在初始化之前需要对特殊情况(要缓存的数据是对象,且没指定缓存名称的时候,意味着要替换掉原来的整个缓存。比如:$.data(elem,{"name": "chua"})。)处理。如果cache[ id ].data不存在则初始化他。

    //使用对象的话用来替代key/value这种成对的方式。
//比如$.data(document,{name:'chenhua'})
    //添加后,直接通过$.data(document,'name')就可以获取
    if ( typeof name === "object" || typeof name === "function" ) {
        //内部使用的时候
        if ( pvt ) {
            //内部使用直接添加到cache[ id ]上
            cache[ id ] = jQuery.extend( cache[ id ], name );

        } else {
            //添加缓存数据到cache[ id ].data上
            cache[ id ].data = jQuery.extend( cache[ id ].data, name );
        }
    }
    thisCache = cache[ id ];

    //非内部使用
    if ( !pvt ) {
        //如果.data没有初始化则先初始化
        if ( !thisCache.data ) {
            thisCache.data = {};
        }

        thisCache = thisCache.data;
    }
View Code

  4.如果是存数据,则存之,返回整个缓存;取数据则取之,返回取得的数据

    //添加缓存数据
    if ( data !== undefined ) {
        thisCache[ jQuery.camelCase( name ) ] = data;
    }

    //如果name是字符串,则返回对应的数据,否则返回整个缓存
    if ( getByName ) {
        //通过本身neme或不同浏览器的驼峰写法获取缓存
        ret = thisCache[ name ];
        if ( ret == null ) {
            ret = thisCache[ jQuery.camelCase( name ) ];
        }
    } else {
        ret = thisCache;
    }
    return ret;
View Code

  ok。流程就到这里。

 

   解析完jQuery.data的流程以后我们来做一组实验,区分高级api:$(...).data和底层api:jQuery.data的区别。在jQuery的官方文档中,提示用户jQuery.data是一个低级的方法,应该用$(...).data()方法来代替。$.data( element, key, value )可以对DOM元素附加任何类型的数据,但应避免循环引用而导致的内存。

var t1=$(document);
var t2=$(document);

//=======第一组$(…).data()方法
t1.data('age',0);
t2.data('age',1);
t1.data('age')  //1
t2.data('age')  //1

//=======第二组$.data()方法
$.data(t1,"name","chua")
$.data(t2,"name","yling")
$.data(t1,"name")   //chua
$.data(t2,"name")   //yling

  可以看出其中的不同吧。使用$(...).data最终传递给internalData的第一个参数elem是节点对象document,所以使用全局缓存jQuery.cache保存。而使用$.data方式最终传递给internalData的第一个参数elem是jQuery对象[document],这样的属性结果是保存在对象自己身上,而t1和t2是不同的对象,分别取自己对象上的缓存,结果当然不同了。

  var t1=$(document);t1.data('age',0);的缓存效果

  

  $.data(t1,"name","chua")的缓存效果

  

  

b. 删除缓存


  使用$(...).data(key,value)方式保存的缓存直接使用$(...).removeData(key)来删除缓存。当然也可以使用低级方法$.data(elem,key,value)来缓存数据,使用$.removeData(elem,key)来删除缓存,但是建议不要使用低级方法。删除缓存最终会调用内部函数internalRemoveData( elem, name, pvt/*内部使用,默认为false*/ )来处理。我们跟踪一下处理流程

  1.判断如果元素不支持添加属性的直接返回。取出缓存容器和相应的id。如果缓存容器不存在则直接返回

        //元素不支持添加属性的直接返回
        if ( !jQuery.acceptData( elem ) ) {
            return;
        }

        var i, l, thisCache,
            isNode = elem.nodeType,
            //详细信息查看jQuery.data源码
            cache = isNode ? jQuery.cache : elem,
            id = isNode ? elem[ jQuery.expando ] : jQuery.expando;

        //缓存中没有数据直接返回
        if ( !cache[ id ] ) {
            return;
        }
View Code

  2.如果有需要删除的缓存名称(参数name,可以是字符串【如果要删除多个缓存名指定的缓存可以使用空格隔开】,也可以是数组),则根据名称删除元素。删除后如果缓存不是空对象则返回。

        if ( name ) {
            //pvt内部使用,默认为false(undefined)
            thisCache = pvt ? cache[ id ] : cache[ id ].data;
            //缓存对象
            if ( thisCache ) {
                //name为字串
                if ( !jQuery.isArray( name ) ) {

                    // name就是一个key
                    if ( name in thisCache ) {
                        name = [ name ];
                    } else {

                        //处理ie兼容,如果name是空格相连的字符串,使用split分割
                        name = jQuery.camelCase( name );
                        if ( name in thisCache ) {
                            name = [ name ];
                        } else {
                            name = name.split(" ");
                        }
                    }
                //name为数组
                } else {
                    //如果name是key的数组
                    // 当数据初始化完成,通过("key", "val")签名,
                    // keys将转化成骆驼写法.
                    // 如果没有办法告知_how_这种key被添加,移除原始类型的key和骆驼写法的key
                    name = name.concat( jQuery.map( name, jQuery.camelCase ) );
                }

                for ( i = 0, l = name.length; i < l; i++ ) {
                    delete thisCache[ name[i] ];
                }

                //如果缓存不为空则返回,如果为空则我们在后面通过delete删除该对象
                if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
                    return;
                }
            }
        }
View Code

  3.如果没有传递要删除的缓存名,表示要删除全部缓存。删除缓存后如果缓存容器不为空对象则返回

        //详细信息查看jQuery.data源码
        if ( !pvt ) {
            delete cache[ id ].data;
            //cache不为空则返回
            if ( !isEmptyDataObject( cache[ id ] ) ) {
                return;
            }
        }
View Code

  4.走到最后一步,表示缓存容器内的缓存都被清空了,删除缓存容器

        //走到这一步表示cache中没有数据了,销毁cache
        if ( isNode ) {
            jQuery.cleanData( [ elem ], true );

        // 当支持删除expandos 或'cache'不是window (#10080)
        } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
            delete cache[ id ];

        // 当所有都失败时,处理为null
        } else {
            cache[ id ] = null;
        }
View Code

  附上完整源码

function internalRemoveData( elem, name, pvt ) {
        //元素不支持添加属性的直接返回
        if ( !jQuery.acceptData( elem ) ) {
            return;
        }

        var i, l, thisCache,
            isNode = elem.nodeType,
            //详细信息查看jQuery.data源码
            cache = isNode ? jQuery.cache : elem,
            id = isNode ? elem[ jQuery.expando ] : jQuery.expando;

        //缓存中没有数据直接返回
        if ( !cache[ id ] ) {
            return;
        }

        if ( name ) {
            //pvt内部使用,默认为false(undefined)
            thisCache = pvt ? cache[ id ] : cache[ id ].data;
            //缓存对象
            if ( thisCache ) {
                //name为字串
                if ( !jQuery.isArray( name ) ) {

                    // name就是一个key
                    if ( name in thisCache ) {
                        name = [ name ];
                    } else {

                        //处理ie兼容,如果name是空格相连的字符串,使用split分割
                        name = jQuery.camelCase( name );
                        if ( name in thisCache ) {
                            name = [ name ];
                        } else {
                            name = name.split(" ");
                        }
                    }
                //name为数组
                } else {
                    //如果name是key的数组
                    // 当数据初始化完成,通过("key", "val")签名,
                    // keys将转化成骆驼写法.
                    // 如果没有办法告知_how_这种key被添加,移除原始类型的key和骆驼写法的key
                    name = name.concat( jQuery.map( name, jQuery.camelCase ) );
                }

                for ( i = 0, l = name.length; i < l; i++ ) {
                    delete thisCache[ name[i] ];
                }

                //如果缓存不为空则返回,如果为空则我们在后面通过delete删除该对象
                if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
                    return;
                }
            }
        }

        //详细信息查看jQuery.data源码
        if ( !pvt ) {
            delete cache[ id ].data;
            //cache不为空则返回
            if ( !isEmptyDataObject( cache[ id ] ) ) {
                return;
            }
        }

        //走到这一步表示cache中没有数据了,销毁cache
        if ( isNode ) {
            jQuery.cleanData( [ elem ], true );

        // 当支持删除expandos 或'cache'不是window (#10080)
        } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
            delete cache[ id ];

        // 当所有都失败时,处理为null
        } else {
            cache[ id ] = null;
        }
    }

 

   如果觉得本文不错,请点击右下方【推荐】!

转载于:https://www.cnblogs.com/chuaWeb/p/jQuery-1-9-1-cache.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要下载 jQuery 1.9.1.min.js,你可以按照以下步骤进行操作: 首先,打开 jQuery 官方网站。在浏览器的地址栏输入"jquery.com",然后按下回车键。 点击页面上的 "Download"(下载)选项。这将会导航至 jQuery 下载页面。 在下载页面中,你可以看到一个包含多个可选择版本的列表。确保选择的是 "1.9.1" 版本。 在 "1.9.1" 版本下面,你会找到 "Download the compressed, production jQuery 1.9.1" 链接。点击该链接。 随即,一个弹出窗口将询问你是否保存或下载文件。选择 "保存文件" 选项,并选择一个合适的文件夹。 保存完毕后,你可以在选择的文件夹中找到并使用 "jquery-1.9.1.min.js" 文件。 现在,你已成功下载了 jQuery 1.9.1.min.js 文件,你可以在你的项目中引入该文件并开始使用 jQuery 的功能了。 ### 回答2: 要下载 jQuery 1.9.1 版本的压缩文件 `jquery-1.9.1.min.js`,可以通过以下步骤进行操作: 1. 首先,在浏览器中打开 jQuery 官方网站(https://jquery.com/)。 2. 在网站的顶部导航菜单中,找到 "Download"(下载)选项,点击进入下载页面。 3. 在下载页面中,你会看到多个版本的 jQuery 可供选择。找到并选择 "1.x" 系列版本,然后点击该系列下的 "Download the compressed, production jQuery 1.9.1"(下载已压缩的 jQuery 1.9.1)链接。 4. 开始下载后,浏览器会提示保存文件的位置和名称。你可以选择保存到自己想要的目录,并将文件名设置为 "jquery-1.9.1.min.js"。 5. 确认保存位置和文件名后,点击 "保存",文件下载就会开始。 下载完成后,你就可以在你选择的目录中找到 "jquery-1.9.1.min.js" 文件了。这个文件是已经压缩过的 jQuery 1.9.1 版本的 JavaScript 文件,你可以将其引入到你的网页中使用 jQuery 的功能。 ### 回答3: jQuery-1.9.1.min.js 是一个 JavaScript 库,用于简化网页开发中常见的操作和任务。这个文件可以通过多种方式进行下载。 首先,可以通过 jQuery 官方网站(https://jquery.com/download/)来下载 jQuery-1.9.1.min.js 文件。在网站上,你可以找到各种不同版本的 jQuery 文件,包括被压缩(minified)的版本和未压缩的版本。选择需要的版本后,点击下载按钮即可下载。 如果你使用的是包管理工具,比如 npm 或者 Yarn,你也可以通过命令行界面来下载 jQuery 文件。打开命令行界面,进入你的项目目录,然后运行适当的命令来下载文件。例如,在使用 npm 的情况下,可以运行命令:```npm install jquery@1.9.1``` 。这样可以将 jQuery-1.9.1.min.js 文件下载到你的项目目录下的 node_modules 文件夹中。 此外,你还可以从第三方网站下载 jQuery-1.9.1.min.js 文件。有很多网站提供 jQuery 文件的下载,包括一些开发工具和资源网站。通过搜索引擎搜索 "jQuery-1.9.1.min.js 下载",你可以找到很多下载链接。请注意,从第三方网站下载文件时,务必选择可信赖的来源,以避免下载到不安全或被修改过的文件。 总而言之,下载 jQuery-1.9.1.min.js 文件的方式有多种,你可以通过 jQuery 官网、包管理工具或者第三方网站来获取该文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值