jQuery源代码解析(1)—— jq基础、data缓存系统

时间:2023-03-09 00:35:28
jQuery源代码解析(1)—— jq基础、data缓存系统

闲话

jquery 的源代码已经到了1.12.0版本号。据官网说1版本号和2版本号若无意外将不再更新,3版本号将做一个架构上大的调整。但预计能兼容IE6-8的。或许这已经是最后的样子了。

我学习jq的时间非常短,应该在1月。那时的版本号还是1.11.3,通过看妙味课堂的公开课视频和文档里的全部api的注解学习。

源代码则是近期些日子直接生啃。跳过了sizzle和文档处理的部分(待业狗压力大。工作以后再看)。关注datareadyeventqueueDefferred(jq的promise编程)、ajaxanimation的处理,初看甚至有点恶心。耐着性子坚持却叹其精妙。在这里记录下来加深印象。

(本文採用 1.12.0 版本号进行解说,用 #number 来标注行号)

jQuery初始化

总体封装上,考虑了两点(写*的重要參考)

模块化支持:通过 noGlobal 变量来决定是否绑定到全局变量,返回值为jQuery

冲突避免:保存window.$ window.jQuery,能够调用noConflict 还原,返回jQuery对象。(noGlobal为true时不须要)

主体使用了例如以下结构。抽离 if 分支。仅仅关注主体逻辑的书写

(function( a, fun ) {
// 推断是否调用、怎样调用fun
})(a, function( _a, _b ) {
// 详细逻辑
}); /* ---- 差别于例如以下模式 ----*/
(function( _a, _b) {
if (推断A) {
// 调整參数或退出
} else if (推断B) {
// 调整參数或退出
} ...
// 这里開始时详细逻辑
})( a );

[源代码]

(function( global, factory ) {

    if ( typeof module === "object" && typeof module.exports === "object" ) {
module.exports = global.document ?
factory( global, true ) :
// 若无window,则模块存为函数,可取出通过传入window来调用一次
// noGlobal为false,一定会污染全局
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
})(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
/* ---- jQuery详细逻辑 ----*/
...
// 对AMD的支持,#10991
if ( typeof define === "function" && define.amd ) {
define( "jquery", [], function() {
return jQuery;
} );
}
// 保存之前的 window.$ window.jQuery
var _jQuery = window.jQuery,
_$ = window.$; jQuery.noConflict = function() {
if ( window.$ === jQuery ) {
window.$ = _$;
}
if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
return jQuery;
};
// 模块化时,不设置全局
if ( !noGlobal ) {
window.jQuery = window.$ = jQuery;
}
return jQuery;
});

jQuery工厂结构

jq为了能有$.func、 $().func两种调用方法,选择了共享 jQuery.prototypereturn new jQuery.fn.init

这并非唯一的方式(能够例如以下),之所以选择如此,个人觉得应该是使用频率太高,这样每次能够省掉两次类型推断。

jQuery.fn 我想也是起到简写、别名的作用

jQuery.extend/fn.extend 则决定了jq重要的插件扩展机制

var jQuery = function( selector, context ) {
if ( this instanceof jQuery) {
return new jQuery( selector, context );
}
// 详细逻辑
}

[源代码]

// #71
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
// #91, 原型的别名 fn,省字符
jQuery.fn = jQuery.prototype = {
...
};
// #175, jQuery 扩展方法extend定义,仅仅填一个參数表示对this进行扩展
jQuery.extend = jQuery.fn.extend = function(){
...
};
// #2866, 支持选择器,节点html,element,jq对象,函数
var init = jQuery.init = function( selector, context, root ) {
...
};
// #2982, 跟jQuery共享原型对象
init.prototype = jQuery.fn;

jQuery链式调用

精髓:通过 return this , return this.pushStack() , return this.prevObject 实现链式调用、增栈、回溯

[源代码]

// # 122, 创建一层新的堆栈, 并引用 prevObject
pushStack: function( elems ) {
// merge -> #433, 支持把数组、类数组的0-length项加入到第一个參数
var ret = jQuery.merge( this.constructor(), elems ); ret.prevObject = this;
ret.context = this.context; return ret;
}; // #164, 能够回溯上一级 preObject
end: function() {
return this.preObject || this.constructor();
} // #3063, 加入到新层 this.constructor()
add: function( selector, context ) {
return this.pushStack(
// 去重排序
jQuery.uniqueSort(
// 合并到 this.get()
jQuery.merge( this.get(), jQuery( selector, context ) )
)
);
},
addBack: function( selector ) {
// add将把结果pushStack
return this.add( selector == null ? this.preObject :
// 可做一次过滤
this.prevObject.filter( selector )
);
}

看到这,说明你真的是个有耐心的boy了。我们上主菜!


IE内存泄露

讲data缓存前,首先要必须介绍一下IE6-8的内存泄露问题。

IE低版本号dom採用COM组件的形式编写,垃圾清理机制。使用的引用计数的方式,无法正确的处理包括dom元素的环状的循环引用。

即使刷新页面内存也不会被释放。

一般是怎样产生循环引用的呢?

此例中。当前作用域里包括变量a (引用了dom元素),通过绑定事件onclick,使dom引用了事件函数。

可是函数在声明时,会把当前所在活动对象的作用域链保存在自身的scope属性。从而引用了当前环境定义的全部变量。而变量a指向dom。从而产生循环引用。须要手动把变量对dom的引用解除

{ // 某个scope作用域
var a = document.getElementById('id_a');
// dom -> fun
a.onclick = function(e) {
};
// fun -> a -> dom , 解除对dom元素的引用, fun -> a -X- dom
a = null;
}

jQuery.data原理

jq内部实现了一个缓存机制,用于分离dom对函数等对象的引用(函数对dom的引用取决于函数本身的特性、定义的位置,这个没法子)。怎样能做到呢?与 策略模式 的思路一致——字符串约定。

jQuery.extend({
// #247, 构造一个基本不可能被占用的属性, 除去"."
expando: "jQuery" + ( version + Math.random() ).replace(/\D/g, ""),
// #507, 全局使用的 guid 号码
guid: 1,
// #4014, 缓存空间
cache: {}
}); /* ---- 原理示意 ---- */
var a = document.getElementById("id_a"); // 每一个元素的 jQuery.expando 分到唯一的 guid 号码
a[jQuery.expando] = a[jQuery.expando] || jQuery.guid++; // jQuery.cache 上元素的 guid 相应的位置为该对象的缓存空间
jQuery.cache[a[jQuery.expando]] = {};

通过 jQuery.data 的封装能够轻松实现数据的存取,可是这样就结束了么?太天真了!

这样做也会产生衍生的问题。尽管不产生循环引用,但对象绑定在jQuery.cache上。即使dom对象被移除。数据仍然不会因此消失。并且假设是函数,仍然会通过引用环境的变量单向引用着dom,导致dom元素也不消失。尽管刷新后内存会清出,不如循环引用严重,但也是问题了。

看起来仍然须要手动设置变量为null,仿佛回到原点,可是数据还在,比之前更不如。解决方法事实上非常easy,就是移除节点时调用 jQuery.cleanData ,data没有被不论什么对象引用,自然的回收。

可是问题仍然没解决,由于比如绑定事件。即使函数放在jQuery.cache中,也至少有一个触发函数绑定在dom上。因此 jQuery.event.add( elem, types, handler, data, selector ) 中的elem返回前被设为null ,见源代码 #4961。所以如非事事通明。尽量使用jq提供的方式删除节点、绑定事件等

function remove( elem, selector, keepData ) { // #6107, #6255为正式方法
var node,
elems = selector ? jQuery.filter( selector, elem ) : elem,
i = 0; for ( ; ( node = elems[ i ] ) != null; i++ ) { if ( !keepData && node.nodeType === 1 ) {
// 清除数据
jQuery.cleanData( getAll( node ) );
} if ( node.parentNode ) {
if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
setGlobalEval( getAll( node, "script" ) );
}
// 删除节点
node.parentNode.removeChild( node );
}
} return elem;
}

jQuery架构方法

jQuery在此之上考虑了几点:

核心

  1. 对普通对象和dom对象做了区分。由于普通对象的垃圾回收机制(GC)使用的不是引用计数,不会内存泄露。

    其次data机制本身添加了复杂度不说,对象解除引用后绑定的数据还须要手动销毁。反而造成内存的无端占用。

    普通对象绑定在自身的 jQuery.expando 属性上。初始化为 { toJSON: jQuery.noop },防止序列化时暴露数据。

    dom对象绑定在 jQuery.cache[ ele[jQuery.expando] ] ,初始化为 {}

  2. 私用和公用隔离,如 jQuery.cache[ guid ]、jQuery.cache[ guid ].data。内部的事件、队列等等都使用的私有空间调用 _data 方法,而用户储存数据调用的 data 方法则是私有,由參数 pvt 决定,true代表私有

  3. 当移除数据后。jQuery.cache[ guid ]或 jQuery.cache[ guid ].data对象不再有数据。则移除该对象释放内存。

    且当移除缓存对象时,绑定在elem的事件函数也将被移除

特性

  1. 支持通过简单的自己定义配置来添加不支持缓存的特例类型

  2. 扩展支持读取 elem.attributes 中 data- 前缀定义的数据

  3. 以小驼峰书写为标准读取方式,内部进行相应转换

jQuery.data结构

使用常见的外观模式。定义核心功能。通过再封装实现个性化的使用规则,以供其它模块或外部调用

  • 核心功能:(特点:參数多而全,逻辑负责全面)

    jQuery.internalData( elem, name, data, pvt ) 为dom和对象设置data数据。支持 -> 核心12
    jQuery.internalRemoveData( elem, name, pvt ) 移除dom或对象上指定属性的data数据-> 核心3
    jQuery.cleanData( elems, forceAcceptData ) 由于删除data时还要删除elem本身上绑定的触发事件函数,因此不能简单 delete cache[id]。
    单独封装,被事件系统和 jQuery.internalRemoveData使用 ->核心3
  • 外观:(工具方法、实例方法)

    jQuery.hasData( elem ) 直接在相应的 `cache` 中查找。
    jQuery._data( elem, name, data ) jQuery.\_removeData( elem, name )`用于内部私有调用。封装了核心方法,简化了传參数量。
    jQuery.data( elem, name, data ) jQuery.removeData( elem, name ) 用于外部公共调用。封装了核心方法,简化了传參数量。 jQuery.fn.data( key, value ) jQuery.fn.removeData( key, value ) 封装了 jQuery.data jQuery.removeData ,遍历this来分别调用
  • 钩子:埋在某个环节直接运行,依据须要实现终止流程、改变特性的功能,或不产生不论什么影响

    acceptData( elem ) #3762,特性1。过滤
    dataAttr( elem, key, data ) #3780。特性2,data的值不存在则会訪问元素的attribute。返回data,且值会缓存到cache
    jQuery.camelCase( string ) #356,特性3。小驼峰

[核心 + 外观 + 钩子] 感觉应该算一种常见好用的架构方式了。

jq中用到了非常多 jQuery.some() -> 核心, jQuery.fn.some() -> 外观 的形式,也都几乎相同。

这里提一下我所理解的钩子,Callback的四个字符串特性就相似于四个钩子。在最常见的几个需求分歧上埋设,发展了观察者模式的4种特性支持,event的众多钩子 标准方式转换 + 个例bug修正 完毕了兼容的大业与主逻辑的统一。

另外不得不提一个在此维度之外的优化方案 —— 提炼反复代码

核心逻辑是不可省的。通常支持数种方式定义參数,把其变化为某种固定形式的參数传入来运行递归是分离函数内部个性化与核心逻辑的第一步,还能够进一步,抽出此部分逻辑。保持主逻辑的纯粹,变成外观部分内的逻辑进行 功能增强。jq再进了一步,由于大量方法都支持參数推断存取、对象或字符串均可、map映射,分离出 access( elems, fn, key, value, chainable, emptyGet, raw ) #4376,核心 jQuery.xxx(),外观jQuery.fn.xxx() 里调用 access`。

// access( elems, fn, key, value, chainable, emptyGet, raw )
// 直观语义是让forEach( elems, fn( elem, key, value ) ), 支持类型推断实现映射等功能 css: function( name, value ) { // #7340, 任意举的样例 // 第二个參数要么是 jQuery.someThing
// 或者是封装了 jQuery.someThing.apply( ... ) 的函数。内含
return access( this, function( elem, name, value ) {
var styles, len,
map = {},
i = 0; // 新增一样仅仅针对css的个性化參数处理
if ( jQuery.isArray( name ) ) {
styles = getStyles( elem );
len = name.length; for ( ; i < len; i++ ) {
map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
} return map;
} return value !== undefined ?
jQuery.style( elem, name, value ) :
jQuery.css( elem, name );
}, name, value, arguments.length > 1 );
}

可是,data缓存系统并没有这样使用。

原因也非常easy,基础的共性部分支持不同不支持映射,如上面的css是共性同样的情况下能够添加个性,但不同的情况就要又一次抽出、或写在外观里、或写在核心代码里使用递归。

[源代码]

/* ---------------------------------- 1. 相关代码(扫一下) ---------------------------------- */

jQuery.extend({
// #247, 构造一个基本不可能被占用的属性, 除去"."
expando: "jQuery" + ( version + Math.random() ).replace(/\D/g, ""),
// #507, 全局使用的 guid 号码
guid: 1,
// #4014, 缓存空间
cache: {},
noData: {
// 为 true 的不能够
"applet ": true,
"embed ": true, // ...but Flash objects (which have this classid) *can* handle expandos
"object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
},
}); // #3748, support.deleteExpando
( function() {
var div = document.createElement( "div" ); // Support: IE<9
support.deleteExpando = true;
try {
delete div.test;
} catch ( e ) {
support.deleteExpando = false;
} // Null elements to avoid leaks in IE.
div = null;
} )(); // #284, 可用于检測 elem 的公有 cache.data 是否已空
jQuery.isEmptyObject = function( obj ) {
var name;
for ( name in obj ) {
return false;
}
return true;
}; // #3814, 检測 elem 的私有 cache 缓存是否已空
function isEmptyDataObject( obj ) {
var name;
for ( name in obj ) { // if the public data object is empty, the private is still empty
if ( name === "data" && jQuery.isEmptyObject( obj[ name ] ) ) {
continue;
}
if ( name !== "toJSON" ) {
return false;
}
} return true;
} /* ---------------------------------- 2. 钩子(瞅瞅) ---------------------------------- */ // #3762
var acceptData = function( elem ) { // 比对禁止列表 jQuery.noData, 为 true 必定不能
var noData = jQuery.noData[ ( elem.nodeName + " " ).toLowerCase() ],
// 普通对象都会按 nodeType = 1 处理,通过筛选
nodeType = +elem.nodeType || 1; return nodeType !== 1 && nodeType !== 9 ? false : // noData不存在(黑名单) 或 存在且classid与noDta同样但不为true(白名单)
!noData || noData !== true && elem.getAttribute( "classid" ) === noData;
}; function dataAttr( elem, key, data ) { // 正确姿势: dataAttr( elem, key, jQuery.data( elem )[ key ] );
// data 不存在。则查找 HTML5 data-* attribute,并存入 jQuery.data( elem, key, data )
// 返回 data
if ( data === undefined && elem.nodeType === 1 ) { var name = "data-" + key.replace( /([A-Z])/g, "-$1" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) {
try {
// "true" -> true , "false" -> false , "23" -> 23
// "{ \"a\": 1}" -> {"a": 1}, "[1, '2']" -> [1, '2']
// 或 data 本身
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
+data + "" === data ? +data :
/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/.test( data ) ? jQuery.parseJSON( data ) :
data;
} catch ( e ) {} // 保存
jQuery.data( elem, key, data ); } else {
data = undefined;
}
} return data;
} // #356, 正则变量替换了,本身在 #80
jQuery.camelCase = function( string ) {
// -ms-abc -> msAbc , a-abc -> aAbc
return string.replace( /^-ms-/, "ms-" ).replace( /-([\da-z])/gi, fcamelCase );
}; /* ---------------------------------- 3. 核心(关键) ---------------------------------- */ // #3830, 加入缓存
function internalData( elem, name, data, pvt /* true 为私,存cache;false 为公。存cache.data */ ) {
// 钩子。黑白名单
if ( !acceptData( elem ) ) {
return;
} var ret, thisCache,
internalKey = jQuery.expando, // dom 和 object 区分对待
isNode = elem.nodeType, // object 不缓存,存自身 jQuery.expando 属性上
cache = isNode ? jQuery.cache : elem, // 没设置过说明 第一次
id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; // 1. 第一次仅仅能写,不能读。否则 return
if ( ( !id || !cache[ id ] || ( !pvt && !cache[ id ].data ) ) &&
data === undefined && typeof name === "string" ) {
return;
} // 2. 第一次写。先初始化 [ 属性、缓存对象 cache ]
// dom -> jQuery.cache[ elem[ internalKey ] ]。object -> object[internalKey]
if ( !id ) { // Only DOM nodes need a new unique ID for each element since their data
// ends up in the global cache
if ( isNode ) {
id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
} else {
id = internalKey;
}
} if ( !cache[ id ] ) {
cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
} // 3. 写入与读取
// write特例:支持 name 參数为 { "key" : value } 或 函数。存入数据
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 );
}
} thisCache = cache[ id ]; // thisCache 依据pvt索引到了应该被赋值的对象
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" ) { // 这不是重点。也能够读非驼峰。正常使用并不会有
ret = thisCache[ name ]; if ( ret == null ) { // 读这里。不管读写,都要读一次
ret = thisCache[ jQuery.camelCase( name ) ];
}
} else {
// 无 name , 直接读 cache
ret = thisCache;
} return ret;
} // #3922, 删除 公/私 缓存属性 或 缓存。 // 删完属性若变空cache,将移去。会处理掉 elem 上 绑定的 event
function internalRemoveData( elem, name, pvt ) { // 钩子,黑名单者,直接 return
if ( !acceptData( elem ) ) {
return;
} var thisCache, i,
isNode = elem.nodeType,
cache = isNode ? jQuery.cache : elem,
id = isNode ? elem[ jQuery.expando ] : jQuery.expando; // 缓存空间未初始化,return
if ( !cache[ id ] ) {
return;
} // 1. name 存在,删除 name 属性值
if ( name ) { thisCache = pvt ? cache[ id ] : cache[ id ].data; if ( thisCache ) { // 1.1 支持数组定义多属性,此处把字符串形式也转为数组[name]
// next step: 统一迭代删除
if ( !jQuery.isArray( name ) ) { // 这不是重点。也能够读非驼峰。 正常使用并不会有
if ( name in thisCache ) {
name = [ name ];
} else { // 看这里,转换为小驼峰读
name = jQuery.camelCase( name );
if ( name in thisCache ) {
name = [ name ];
} else {
// 能够字符串空格隔开多个,均变成小驼峰
name = name.split( " " );
}
}
} else { // If "name" is an array of keys...
// When data is initially created, via ("key", "val") signature,
// keys will be converted to camelCase.
// Since there is no way to tell _how_ a key was added, remove
// both plain key and camelCase key. #12786
// This will only penalize the array argument path.
name = name.concat( jQuery.map( name, jQuery.camelCase ) );
} // 1.2 删
i = name.length;
while ( i-- ) {
delete thisCache[ name[ i ] ];
} // 1.3 假设 cache 删除后没空。结束 return
// 假设空了, 与 name 不存在的情况一样直接删除 data
if ( pvt ? !isEmptyDataObject( thisCache ) : !jQuery.isEmptyObject( thisCache ) ) {
return;
}
}
} // 2. 依据 pvt 推断,false 删除公有
if ( !pvt ) {
delete cache[ id ].data; // cache 还没空,能够闪了,return
// cache 空了,合并到 pvt 为true,私有cache 删除
if ( !isEmptyDataObject( cache[ id ] ) ) {
return;
}
} // 3. pvt 为 true, 删除私有 cache
// 3.1 为节点时。若cache[events]里还有事件,把 elem 绑定的事件函数删除
if ( isNode ) {
jQuery.cleanData( [ elem ], true ); // 3.2 普通对象时
} else if ( support.deleteExpando || cache != cache.window ) {
// 能删则 delete
delete cache[ id ]; // 不能删, undefined
} else {
cache[ id ] = undefined;
}
} // #6192, 函数内包括 删除事件队列时删除elem相应type的绑定事件 的功能
// cleanData 不仅被 internalRemoveData 内部调用,remove节点的时候也$().remove调用,因此支持了elems 数组
jquery.cleanData = function( elems, /* internal */ forceAcceptData ) {
var elem, type, id, data,
i = 0,
internalKey = jQuery.expando,
cache = jQuery.cache,
attributes = support.attributes,
special = jQuery.event.special; // 支持 elems 数组迭代
for ( ; ( elem = elems[ i ] ) != null; i++ ) {
// 钩子
if ( forceAcceptData || acceptData( elem ) ) { id = elem[ internalKey ];
data = id && cache[ id ]; if ( data ) {
// 1. 存在事件队列
if ( data.events ) { // 迭代,删除绑在 elem 上的触发函数
for ( type in data.events ) { // 尽管绑定在data上的事件。都转换成标准的 eventType
// 但标准的 eventtype 可能不被兼容
// special.setup 钩子在绑定触发函数时会hack一次
// 须要该方法找到绑定在elem上事件触发函数的真正类型并删除
if ( special[ type ] ) {
jQuery.event.remove( elem, type ); // 普通情况,直接删除
} else {
jQuery.removeEvent( elem, type, data.handle );
}
}
} // 2. 不存在(或已删去)事件队列
if ( cache[ id ] ) { delete cache[ id ]; // Support: IE<9
// IE does not allow us to delete expando properties from nodes
// IE creates expando attributes along with the property
// IE does not have a removeAttribute function on Document nodes
if ( !attributes && typeof elem.removeAttribute !== "undefined" ) {
elem.removeAttribute( internalKey ); } else {
elem[ internalKey ] = undefined;
} deletedIds.push( id );
}
}
}
}
} /* ---------------------------------- 4. 外观(接口API) ---------------------------------- */ // #4013
jQuery.extend( {
// cache、noData 上面已经提前写了
cache: {},
noData: {
"applet ": true,
"embed ": true,
"object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
}, // 直接从缓存 cache 查找推断。dom 与 object差别对待
hasData: function( elem ) {
elem = elem.nodeType ? jQuery.cache[ elem[ jQuery.expando ] ] : elem[ jQuery.expando ];
return !!elem && !isEmptyDataObject( elem );
}, // 公用。pvt 无
data: function( elem, name, data ) {
return internalData( elem, name, data );
}, removeData: function( elem, name ) {
return internalRemoveData( elem, name );
}, // 私用,pvt true
_data: function( elem, name, data ) {
return internalData( elem, name, data, true );
}, _removeData: function( elem, name ) {
return internalRemoveData( elem, name, true );
}
} ); // #4049,实例方法
jQuery.fn.extend( {
data: function( key, value ) {
var i, name, data,
elem = this[ 0 ],
attrs = elem && elem.attributes; // Special expections of .data basically thwart jQuery.access,
// so implement the relevant behavior ourselves // 1. key不存在。 获得 data缓存 全部值
if ( key === undefined ) {
if ( this.length ) {
data = jQuery.data( elem ); if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
i = attrs.length;
while ( i-- ) { // Support: IE11+
// The attrs elements can be null (#14894)
if ( attrs[ i ] ) {
name = attrs[ i ].name;
if ( name.indexOf( "data-" ) === 0 ) {
name = jQuery.camelCase( name.slice( 5 ) ); // 钩子,data[name]若无。则搜索data- attribute,并赋值给 data[ name ]
dataAttr( elem, name, data[ name ] );
}
}
}
jQuery._data( elem, "parsedAttrs", true );
}
} return data;
} // 2. key 是 "object"
// internalData 已经支持了 "object" 參数。因此直接迭代
if ( typeof key === "object" ) {
return this.each( function() {
jQuery.data( this, key );
} );
} return arguments.length > 1 ? // 3. 迭代写入 key -> value
this.each( function() {
jQuery.data( this, key, value );
} ) : // 4. 读取 key 属性值, 没有则尝试读data- attribute,并赋值给 data[ name ]
elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
}, removeData: function( key ) {
return this.each( function() {
jQuery.removeData( this, key );
} );
}
} );

最后再说几点:

  1. $(elem).data("key", "name")$.data($(elem), "key", "name") 的差别是前者内部元素被迭代。绑定在元素(dom)上,而后者绑定在 $(elem) 对象(object)上,差别不言自明。

  2. 对于支持对象等多种參数形式的逻辑本身很多其它放在外观里。这里在 internalData。由于公有私有不止一个外观,避免反复要么抽出相似 access 使用。要么放到公共方法中。而之所以不放在终于的实例 data 方法中,由于工具方法已经在jq模块内部被多次使用了,这样能够有效简化内部操作。

  3. dataAtrr 之所以在实例 data 方法中才出现被使用。是由于仅仅实用户调用的时候dom才载入完呀,才会产生这个须要。