jQuery中很多其他组件都用到data功能,源码与注解如下
(function (jQuery) {
var rbrace = /^(?:\{.*\}|\[.*\])$/, //非获取匹配,数组或者json对象
rmultiDash = /([A-Z])/g;
jQuery.extend({
cache: {},
uuid: 0,
expando: "jQuery" + (jQuery.fn.jQuery + Math.random()).replace(/\D/g, ""),
noData: {
"embed": true,
"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
"applet":true
},
hasData: function (elem) {
elem = elem.nodeType ? jQuery.cache[elem[jQuery.expando]] : elem[jQuery.expando];
return !!elem && !isEmptyDataObject(elem);
},
data: function (elem, name, data, pvt/*仅jQuery内部使用*/) {
if (!jQuery.acceptData(elem)) {
return;
}
//privateCache 数据缓存对象的属性data
//thisCache 数据缓存对象
//如果pvt为true privateCache与thisCache都是指向数据缓存对象
var privateCache, thisCache, ret,
internalKey = jQuery.expando,//jQuery21004586855585473266
getByName = typeof name === "string",
//处理Dom元素的数据问题,防止内存泄露
isNode = elem.nodeType,//1-9 Dom元素
//缓存的数据来源:Dom元素需要使用jQuery的cache,其他元素不需要
cache = isNode ? jQuery.cache : elem,
id = isNode ? elem[internalKey] : elem[internalKey] && internalKey,//Dom节点第一次存数据是没有id的
isEvents = name === "events";
//如果是读取数据,但是没有数据,则直接返回
//后面getByName&&data===undefined 如果name是字符串、data是undefined则说明是在读取数据
//前面其他的 如果关联id不存在,说明没有数据;如果cache[id] 不存在,也说明没有数据,如果是读取自定义数据,但cache[id].data不存在,说明没有自定义数据
if ((!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined) {
return;
}
//如果关联id不存在则分配一个
if (!id) {
if (isNode) {
//仅Dom节点元素需要uuid
elem[internalKey] = id = ++jQuery.uuid;
} else {
id = internalKey;
}
}
//如果数据缓存对象不存在,则初始化它
if (!cache[id]) {
cache[id] = {};
//避免非Dom节点对象被JSON.stringfy序列化的时候,暴露缓存数据
if (!isNode) {
cache[id].toJSON = jQuery.noop; //jQuery.noop 即 function(){}
}
}
//传入一个对象或者函数而不是键值对,则批量设置数据
if (typeof name === "object" || typeof name === "function") {
if (pvt) {//内部数据
cache[id] = jQuery.extend(cache[id], name);
} else {
cache[id].data = jQuery.extend(cache[id].data, name);
}
}
//先都指向数据缓存对象=====>说明以上都是在处理id问题
privateCache = thisCache = cache[id];
if (!pvt) {
//如果自定数据缓存对象cache[id].data不存在,则先将其初始化空对象
if (!thisCache.data) {
thisCache.data = {};
}
thisCache = thisCache.data;
}
//如果参数data不是undefined,则把参数data设置到属性name上
if (data !== undefined) {
//这时thisCache已经为privateCache.data了,现在只是在data上加属性值了
thisCache[jQuery.camelCase(name)] = data;
}
//特殊处理events,如果参数name是字符串events,并且未设置自定义数据events,则返回事件缓存对象,在其中存储了事件监听函数
if (isEvents && !thisCache[name]) {
return privateCache.events;
}
//如果参数name是字符串,则读取单个数据
//在这个过程中,会先尝试读取参数name对应的数据,如果未取到,则把参数name转换为驼峰式再次尝试读取
//所以如果你使用 $.data(elem,name,data)的时候,会返回data
if (getByName) {
ret = thisCache[name];
if (ret == null) {
ret = thisCache[jQuery.camelCase(name)];
}
} else {
//如果未传入参数name、data,则返回数据缓存对象
ret = thisCache;
}
return ret;
},
removeData: function (elem, name, pvt/*仅jQuery内部使用*/) {
if (!jQuery.acceptData(elem)) {
return;
}
var thisCache, i, l,
internalKey = jQuery.expando,
isNode = elem.nodeType,
cache = isNode ? jQuery.cache : elem,
id = isNode ? elem[internalKey] : internalKey;
//当cache[id]没有数据时,即缓存中没有数据的时候,就直接返回
if (!cache[id]) {
return;
}
if (name) {
thisCache = pvt ? cache[id] : cache[id].data;
if (thisCache) {
if (!jQuery.isArray(name)) {
//当name 不是数组的时候
if (name in thisCache) {
name = [name];
} else {
name = jQuery.camelCase(name);
if (name in thisCache) {
name = [name];
} else {
//name是这个形式 "age name"
name = name.split(" ");
}
}
}
//批量删除
for (i = 0, l = name.length; i < l; i++) {
delete thisCache[name[i]];
}
//判断删除后的缓存数据是不是空对象
if (!(pvt ? isEmptyDataObject : jQuery.isEmptyObject)(thisCache)) {
return;
}
}
}
if (!pvt) {
//当缓存数据为空对象的时候
delete cache[id].data;
if (!isEmptyDataObject(cache[id])) {
return;
}
}
//确保cache不是window对象
if (jQuery.support.deleteExpando || !cache.setInterval) {
delete cache[id];
} else {
cache[id] = null;
}
//当为Dom节点情况的时候,到这里说明Dom节点上的数据已经为空了 所以要删除Dom上的id值
if (isNode) {
if (jQuery.support.deleteExpando) {
delete elem[internalKey];
} else if (elem.removeAttribute) {
elem.removeAttribute(internalKey);
} else {
elem[internalKey] = null;
}
}
},
//仅jQuery内部使用
_data: function (elem, name, data) {
return jQuery.data(elem, name, data, true);
},
//判断Dom节点是否可以保存data expando信息
acceptData: function (elem) {
if (elem.nodeName) { //这里可以知道elem不是jquery对象,它是一个Dom元素或者普通对象等
var match = jQuery.noData[elem.nodeName.toLowerCase()];
if (match) {
return !(match === true || elem.getAttribute("classid") !== match);
}
}
return true;
}
});
//对jQuery实例的扩展
jQuery.fn.extend({
data: function (key, value) {
var parts, attr, name,
data = null;
if (typeof key === "undefined") {
if (this.length) {
//jQuery对象转换为普通对象,调用jQuery.data
data = jQuery.data(this[0]);
//当为Dom节点的时候处理方式
if (this[0].nodeType === 1 && !jQuery._data(this[0], "parsedAttrs")) {
attr = this[0].attributes;
for (var i = 0, l = attr.length; i < l; i++) {
name = attr[i].name;
//html5属性标签 data-***
if (name.indexOf("data-") === 0) {
name = jQuery.camelCase(name.substring(5));
dataAttr(this[0], name, data[name]);
}
}
jQuery._data(this[0], "parsedAttrs", true);
}
}
return data;
} else if (typeof key === "object") {
return this.each(function () {
jQuery.data(this, key);
});
}
//为触发自带事件做准备
parts = key.split(".");
parts[1] = parts[1] ? "." + parts[1] : "";
if (value === undefined) {
data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
if (data === undefined && this.length) {
data = jQuery.data(this[0], key);
data = dataAttr(this[0], key, data);
}
return data === undefined && parts[1] ? this.data(parts[0]) : data;
} else {
//jQuery常用的处理方式,因为jQuery对象其实就是一个类数组,故需要用each
return this.each(function () {
var self = jQuery(this),
args = [parts[0], value];
self.triggerHandler("setData" + parts[1] + "!", args);
jQuery.data(this, key, value);
self.triggerHandler("changeData" + parts[1] + "!", args);
});
}
},
removeData: function (key) {
//jQuery常用的处理方式,因为jQuery对象其实就是一个类数组,故需要用each
return this.each(function () {
jQuery.removeData(this, key);
});
}
});
function dataAttr(elem, key, data) {
//如果通过internal没有找到,则去根据 HTML5中的data-* 属性来
if (data === undefined && elem.nodeType === 1) {
var name = "data-" + key.replace(rmultiDash, "-$1").toLowerCase();
data = elem.getAttribute(name);
if (typeof data === "string") {
try{
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
jQuery.isNumeric(data) ? parseFloat(data) :
rbrace.test(data) ? jQuery.parseJSON(data) :
data;
} catch (e) { }
jQuery.data(elem, key, data);
} else {
data = undefined;
}
}
return data;
}
//检查一个缓冲对象是否为空对象
function isEmptyDataObject(obj) {
for (var name in obj) {
//如果公共的数据对象为空,那么私有的必然也是为空
// isEmptyObject: function( obj ) {
// for ( var name in obj ) {
// return false;
// }
// return true;
//}
if (name === "data" && jQuery.isEmptyObject(obj[name])) {
continue;
}
//会附加一个toJSON属性 jQuery.noop
if (name !== "toJSON") {
return false;
}
}
return true;
}
})(jQuery)