前言

最近比较烦,深圳的工作还没着落,论文不想弄,烦。。。。。今天看了下jquery的数据缓存的代码,参考着Aaron的源码分析,自己有点理解了,和大家分享下。以后也打算把自己的jquery的学习心得写一个系列,当然和大神的源码分析是比不了的,只是自己在看的时候有好多地方是比较难理解的,为新手提供些便捷的学习方法,以后我会把我这些流水账整理成一个菜鸟学习jquery源码系列,现在就看到哪写到那,见谅。

内存泄露

首先看看什么是内存泄露,这里直接拿来Aaron中的这部分来说明什么是内存泄露,内存泄露的3种情况:

1 循环引用

2 Javascript闭包

3 DOM插入顺序

在这里我们只解释第一种情况,因为jquery的数据缓存就是解决这类的内存泄露的。一个DOM对象被一个Javascript对象引用,与此同时又引用同一个或其它的Javascript对象,这个DOM对象可能会引发内存泄漏。这个DOM对象的引用将不会在脚本停止的时候被垃圾回收器回收。要想破坏循环引用,引用DOM元素的对象或DOM对象的引用需要被赋值为null。

含有DOM对象的循环引用将导致大部分当前主流浏览器内存泄露

第一种:多个对象循环引用

var a=new Object;

var b=new Object;

a.r=b;

b.r=a;

第二种:循环引用自己

var a=new Object;

a.r=a;

循环引用很常见且大部分情况下是无害的,但当参与循环引用的对象中有DOM对象或者ActiveX对象时,循环引用将导致内存泄露。

我们把例子中的任何一个new Object替换成document.getElementById或者document.createElement就会发生内存泄露了。

在实际应用中我们要给我们的DOM添加数据,如果我们给一个DOM添加的数据太多的话,会存在循环引用的风险,例如我们添加的数据恰好引用了这个DOM元素,就会存在内存的泄露。所以jquery使用了数据缓存的机制就解决或者说避免这一问题。

数据缓存

$.cache 是jquery的缓存对象,这个是对象就是一个json,它的结构是这样的

{ "uid1": { // DOM节点1缓存数据,
"name1": value1,
"name2": value2
},
"uid2": { // DOM节点2缓存数据,
"name1": value1,
"name2": value2
}

数据缓存的接口是

$.data( element, key, value )

$(selector).data(key,value)

用法

看代码之前,先看看怎么使用jquery的数据缓存。在jquery中,有两个方法可以给对象设置数据,分别是实例方法$().data()和静态方法$.data(),具体的使用过程大家看api就知道了,这里简单介绍下

静态方法$.data()有三个参数,分别是挂在数据的元素,挂载的数据键,挂载数据的值,根据参数的不同,无非就是设置数据,取数据,具体如下

1 $.data( elem, key, value ) 在指定元素上存储/添加任意的数据,处理了循环引用和内存泄漏问题
 2 $.data( elem, key ) 返回指定元素上name指定的值
 3 $.data( elem ) 返回全部数据
 4 $.data( elem,obj ) 在指定的元素上绑定obj

var obj = {};
$.data(obj , "a" , 1);//普通对象添加数据
console.log($.data(obj,"a"));//
var dom = $("body");//dom添加数据
$.data(dom,"a",1)
console.log($.data(dom,"a"));//
$.data(obj , {"b":2});//两个参数 绑定数据对象
console.log($.data(dom,"b"));//
console.log($.data(dom));//1 2

静态方法$().data()有两个参数,挂载的数据键,挂载数据的值

1 $(selector).data( key, value ) 在指定元素上存储/添加任意的数据,处理了循环引用和内存泄漏问题
 2 $(selector).data( key ) 返回指定元素上name指定的值
 3 $(selector).data(obj ) 在指定的元素上绑定obj 
 4 $(selector).data() 返回全部数据

$("body").data("a" , 1);//添加数据
console.log($("body").data("a"));//
$("body").data({"b":2});//两个参数 绑定数据对象
console.log($("body").data("b"));//
console.log($("body").data();//1 2

思路

回想下我们要解决什么问题:我们想在DOM上添加数据,但是不想引起内存的泄露,也就是我们不想引起循环引用,要尽量减少在DOM上挂数据。jquery的思路是这样:使用一个数据缓存对象$.cache,在需要绑定数据的DOM上扩展一个expando属性,这个属性存的是一个id,这里不会存在循环引用的情况了,之后将数据存在$.cache[id]上,当我们取DOM上的数据的时候,我们可以根据DOM上的expando找到id,进而找到存在$.cache[id]上的数据。可以看出jquery只是在DOM上扩展了一个属性expando,数据都存在了$.cache中,利用expando这个属性建立DOM和缓存对象之间的联系。无论我们添加多少的数据都会存储在缓存对象中,而不是直接挂在DOM上。这个唯一id是一个整型值,初始为0,调用data接口时自动加一,唯一id附加在以$.expando命名的属性上,$.expando是动态生成的,类似于一个时间戳,以尽可能的避免与用户变量冲突。从匹配的DOM元素上取到唯一id,在$.cache中找到唯一id对应的对象,再从对应的对象中找到key对应的值

看例子,在源码里打断点看一下

$.data($("body")[0],{"a":1});
console.log($.data($("body")[0],"a"));

DOM对象扩展了一个属性,这个属性存的是cache的id。

这样大家就比较明显了。

实现

expando就是一个类似时间戳的东东,源码

expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" )

就是为了生成标识的,没啥可说的。

这是静态方法的代码的整体结构,我看到的1.10.2,变化较大,所有的方法的实现都封装成了函数,主要看 internalData( elem, name, data )这个函数,其他的大伙自己看看吧

jQuery.extend({
cache: {}, // The following elements throw uncatchable exceptions if you
// attempt to add expando properties to them.
noData: {
"applet": true,
"embed": true,
// Ban all objects except for Flash (which handle expandos)
"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
}, hasData: function( elem ) {
elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
return !!elem && !isEmptyDataObject( elem );
}, data: function( elem, name, data ) {
return internalData( elem, name, data );
}, removeData: function( elem, name ) {
return internalRemoveData( elem, name );
}, // For internal use only.
_data: function( elem, name, data ) {
return internalData( elem, name, data, true );
}, _removeData: function( elem, name ) {
return internalRemoveData( elem, name, true );
}, // A method for determining if a DOM node can handle the data expando
acceptData: function( elem ) {
// Do not set data on non-element because it will not be cleared (#8335).
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;
}
});
function internalData( elem, name, data, pvt /* Internal Use Only */ ){
if ( !jQuery.acceptData( elem ) ) {//查看是否可以接受数据
return;
}
var ret, thisCache,
internalKey = jQuery.expando,//jQuery副本的唯一标识
// We have to handle DOM nodes and JS objects differently because IE6-7
// can't GC object references properly across the DOM-JS boundary
isNode = elem.nodeType,//判断DOM节点
// Only DOM nodes need the global jQuery cache; JS object data is
// attached directly to the object so GC can occur automatically
cache = isNode ? jQuery.cache : elem,//若是是DOM对象,则cache就是$.cache,否则为参数elem对象
// Only defining an ID for JS objects if its cache already exists allows
// the code to shortcut on the same path as a DOM node with no cache
id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;//找id,id可能在DOM[expando]中,也可以在elem[expando]中
// 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] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {
return;//参数的一些判断限制
}
if ( !id ) {//id不存在
// Only DOM nodes need a new unique ID for each element since their data
// ends up in the global cache
if ( isNode ) {//是DOM节点
id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;//生成一个id
} else {//不是DOM,是一个对象
id = internalKey;//那么id就是那个expando
}
}
if ( !cache[ id ] ) {//cache中不存在数据,先弄成空的,一会在填充
// Avoid exposing jQuery metadata on plain JS objects when the object
// is serialized using JSON.stringify
cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
}
// An object can be passed to jQuery.data instead of a key/value pair; this gets
// shallow copied over onto the existing cache
if ( typeof name === "object" || typeof name === "function" ) {//处理第二个参数时对象或者是函数的情况
if ( pvt ) {//不太懂
cache[ id ] = jQuery.extend( cache[ id ], name );
} else {//添加到data属性上
cache[ id ].data = jQuery.extend( cache[ id ].data, name );
}
}
thisCache = cache[ id ];
// jQuery data() is stored in a separate object inside the object's internal data
// cache in order to avoid key collisions between internal data and user-defined
// data.
if ( !pvt ) {
if ( !thisCache.data ) {
thisCache.data = {};
}
thisCache = thisCache.data;
}
if ( data !== undefined ) {//第三个参数存在,就是存数据
thisCache[ jQuery.camelCase( name ) ] = data;
}
// Check for both converted-to-camel and non-converted data property names
// If a data property was specified
if ( typeof name === "string" ) { // First Try to find as-is property data
ret = thisCache[ name ];//取出来待返回的那个value
//有啥用 这么麻烦
// Test for null|undefined property data
if ( ret == null ) {
// Try to find the camelCased property
ret = thisCache[ jQuery.camelCase( name ) ];
}
} else {
ret = thisCache;//就是返回存进来的那个对象或者函数
}
return ret;
}

实现起来还是比较简单的,只是有些地方jquery考虑的太周全了,我等凡人看不太透彻。

pS:给DOM对象添加的数据是存储在了$.cache中,而给对象添加书数据直接挂在了对象的expando上面。其实给一个对象挂数据也没有什么实际的意义。

看源码可以知道,看个例子更明显

var obj = {};
$.data(obj,{"a":1});
console.log($.data(obj,"a"));
console.log(obj);

结果:

实例方法data()其实就是调用了$.data()这个静态方法,这里就不说了。

jQuery.fn.extend({
data: function( key, value ) {
var attrs, name,
data = null,
i = 0,
elem = this[0]; // Special expections of .data basically thwart jQuery.access,
// so implement the relevant behavior ourselves // Gets all values
if ( key === undefined ) {
if ( this.length ) {
data = jQuery.data( elem ); if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
attrs = elem.attributes;
for ( ; i < attrs.length; i++ ) {
name = attrs[i].name; if ( name.indexOf("data-") === 0 ) {
name = jQuery.camelCase( name.slice(5) ); dataAttr( elem, name, data[ name ] );
}
}
jQuery._data( elem, "parsedAttrs", true );
}
} return data;
} // Sets multiple values
if ( typeof key === "object" ) {
return this.each(function() {
jQuery.data( this, key );
});
} return arguments.length > 1 ? // Sets one value
this.each(function() {
jQuery.data( this, key, value );//这是重点
}) : // Gets one value
// Try to fetch any internally stored data first
elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
},

问题

现在我们利用源码分析一些问题

        var a = $("body");
var b = $("body");
a.data("a",1);
b.data("a",2);
console.log(a.data("a"));//
console.log(b.data("a"));// $.data(a,"b",1);
$.data(b,"b",2);
console.log($.data(a,"b"))//
console.log($.data(b,"b"))// $.data(a[0],"b",1);
$.data(b[0],"b",2);
console.log($.data(a[0],"b"));//
console.log($.data(b[0],"b"));//

看着有些晕,先看下这个

var a = $("body");
var b = $("body");
console.log(a[0] == b[0]);//true
console.log(a == b);//false
console.log( $("body") == $("body"));//false

每一次$("body")都生成一个新的对象,所以每一次都会不同,$("body")[0]都是指向同一个body对象,a 和b指向的每个新对象的地址,所以不同。

看第一组

        var a = $("body");
var b = $("body");
a.data("a",1);
b.data("a",2);
console.log(a.data("a"));//
console.log(b.data("a"));//

在看源代码这句

this.each(function() {
jQuery.data( this, key, value );
})

调用$.data(),但是这里第一个参数为this,是原生的DOM对象,第一组中的a和b的DOM对象都是body,所以添加数据会产生覆盖现象。

第二组和第二组是正常情况,不解释了。

小结

这就是我的理解,希望大家指正。以后会多分析jquery的实现过程,源码的细节太难了。

【菜鸟学习jquery源码】数据缓存与data()的更多相关文章

  1. 菜鸟的jQuery源码学习笔记(前言)

    前言 相信任何一名前端开发人员或者是前端爱好者都对jQuery不陌生.jQuery简单易用,功能强大,特别是拥有良好的浏览器兼容性,大大降低了前端开发的难度,使得前端开发变得“平易近人起来”.自从本人 ...

  2. 原生JS研究:学习jquery源码,收集整理常用JS函数

    原生JS研究:学习jquery源码,收集整理常用JS函数: 1. JS获取原生class(getElementsByClass) 转自:http://blog.csdn.net/kongjiea/ar ...

  3. js菜鸟进阶-jQuery源码分析(1)-基本架构

    导读: 本人JS菜鸟一枚,为加强代码美观和编程思想.所以来研究下jQuery,有需要进阶JS的同学很适合阅读此文!我是边看代码(jquery2.2.1),边翻“javascript高级程序设计”写的, ...

  4. 菜鸟学习Fabric源码学习 — Endorser背书节点

    Fabric 1.4 源码分析 Endorser背书节点 本文档主要介绍fabric背书节点的主要功能及其实现. 1. 简介 Endorser节点是peer节点所扮演的一种角色,在peer启动时会创建 ...

  5. 菜鸟学习Fabric源码学习 — 背书节点和链码容器交互

    Fabric 1.4 源码分析 背书节点和链码容器交互 本文档主要介绍背书节点和链码容器交互流程,在Endorser背书节点章节中,无论是deploy.upgrade或者调用链码,最后都会调用Chai ...

  6. 菜鸟学习Fabric源码学习 — kafka共识机制

    Fabric 1.4源码分析 kafka共识机制 本文档主要介绍kafka共识机制流程.在查看文档之前可以先阅览raft共识流程以及orderer服务启动流程. 1. kafka 简介 Kafka是最 ...

  7. 菜鸟的jQuery源码学习笔记(二)

    jQuery对象是使用构造函数和原型模式相结合的方式创建的.现在来看看jQuery的原型对象jQuery.prototype: jQuery.fn = jQuery.prototype = { //成 ...

  8. 菜鸟的jQuery源码学习笔记(一)

    整个jQuery是一个自调用的匿名函数: (function(global, factory) { if (typeof module === "object" && ...

  9. 菜鸟的jQuery源码学习笔记(三)

    each: function(callback, args) { return jQuery.each(this, callback, args); }, each:这个调用了jQuery.each方 ...

随机推荐

  1. vs2010设置断点进行调试时不起作用

    1.打开vs2010 2.点击web下的“属性” 3.点击“生成” 4.点击最下方的“高级” 5.在“输出”-调试信息中选择“full”,点击确定按钮即可

  2. Source Insight 常用设置和快捷键大全

    Source Insight 常用设置和快捷键大全 退出程序 : Alt+F4 重画屏幕 : Ctrl+Alt+Space 完成语法 : Ctrl+E 复制一行 : Ctrl+K 恰好复制该位置右边的 ...

  3. Python 基礎 - 列表的使用

    如果想要存所有 Marvel's The Avengers 角色的人名,該如何存呢?請用目前已學到的知識來實做- #!/usr/bin/env python3 # -*- coding:utf-8 - ...

  4. pageHelp的使用

    以前使用ibatis/mybatis,都是自己手写sql语句进行物理分页,虽然稍微有点麻烦,但是都习惯了.最近试用了下mybatis的分页插件 PageHelper,感觉还不错吧.记录下其使用方法. ...

  5. javascript中escape()、unescape()、encodeURI()、encodeURIComponent()、decodeURI()、decodeURIComponent()比较

    这些URI方法encodeURI.encodeURIComponent().decodeURI().decodeURIComponent()代替了BOM的escape()和unescape()方法.U ...

  6. Swift建立栈的泛型结构体以及top()、push()、pop()定义函数的定义

    首先可以使用swift定义Stack的结构体 //泛型表达 struct Stack<T> { var items = <T>() //定义栈顶函数,返回栈顶元素 mutati ...

  7. 传统开发模型vs敏捷开发模型——过程模型的变革

    一.概念框架 在了解一个新概念的时候,最好的方法就是把它插入到原有的概念体系中.在不仅有助于对概念的记忆,更利于深刻地认识概念的本质.精髓.下图说明了"敏捷开发"在软件工程理论体系 ...

  8. Interpolation in MATLAB

    Mathematics     One-Dimensional Interpolation There are two kinds of one-dimensional interpolation i ...

  9. 【OpenGL】第二篇 Hello OpenGL

    ---------------------------------------------------------------------------------------------------- ...

  10. js生成二维码(jquery自带)

    //引入js<script type="text/javascript" src="js/jquery.js"></script> &l ...