闲话

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 分支。仅仅关注主体逻辑的书写

  1. (function( a, fun ) {
  2. // 推断是否调用、怎样调用fun
  3. })(a, function( _a, _b ) {
  4. // 详细逻辑
  5. });
  6. /* ---- 差别于例如以下模式 ----*/
  7. (function( _a, _b) {
  8. if (推断A) {
  9. // 调整參数或退出
  10. } else if (推断B) {
  11. // 调整參数或退出
  12. } ...
  13. // 这里開始时详细逻辑
  14. })( a );

[源代码]

  1. (function( global factory ) {
  2. if ( typeof module === "object" && typeof module.exports === "object" ) {
  3. module.exports = global.document ?
  4. factory( global, true ) :
  5. // 若无window,则模块存为函数,可取出通过传入window来调用一次
  6. // noGlobal为false,一定会污染全局
  7. function( w ) {
  8. if ( !w.document ) {
  9. throw new Error( "jQuery requires a window with a document" );
  10. }
  11. return factory( w );
  12. };
  13. } else {
  14. factory( global );
  15. }
  16. })(typeof window !== "undefined" ?
  17. window : this, function( window, noGlobal ) {
  18. /* ---- jQuery详细逻辑 ----*/
  19. ...
  20. // 对AMD的支持,#10991
  21. if ( typeof define === "function" && define.amd ) {
  22. define( "jquery", [], function() {
  23. return jQuery;
  24. } );
  25. }
  26. // 保存之前的 window.$ window.jQuery
  27. var _jQuery = window.jQuery,
  28. _$ = window.$;
  29. jQuery.noConflict = function() {
  30. if ( window.$ === jQuery ) {
  31. window.$ = _$;
  32. }
  33. if ( deep && window.jQuery === jQuery ) {
  34. window.jQuery = _jQuery;
  35. }
  36. return jQuery;
  37. };
  38. // 模块化时,不设置全局
  39. if ( !noGlobal ) {
  40. window.jQuery = window.$ = jQuery;
  41. }
  42. return jQuery;
  43. });

jQuery工厂结构

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

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

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

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

  1. var jQuery = function( selector, context ) {
  2. if ( this instanceof jQuery) {
  3. return new jQuery( selector, context );
  4. }
  5. // 详细逻辑
  6. }

[源代码]

  1. // #71
  2. var jQuery = function( selector, context ) {
  3. return new jQuery.fn.init( selector, context );
  4. };
  5. // #91, 原型的别名 fn,省字符
  6. jQuery.fn = jQuery.prototype = {
  7. ...
  8. };
  9. // #175, jQuery 扩展方法extend定义,仅仅填一个參数表示对this进行扩展
  10. jQuery.extend = jQuery.fn.extend = function(){
  11. ...
  12. };
  13. // #2866, 支持选择器,节点html,element,jq对象,函数
  14. var init = jQuery.init = function( selector, context, root ) {
  15. ...
  16. };
  17. // #2982, 跟jQuery共享原型对象
  18. init.prototype = jQuery.fn;

jQuery链式调用

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

[源代码]

  1. // # 122, 创建一层新的堆栈, 并引用 prevObject
  2. pushStack: function( elems ) {
  3. // merge -> #433, 支持把数组、类数组的0-length项加入到第一个參数
  4. var ret = jQuery.merge( this.constructor(), elems );
  5. ret.prevObject = this;
  6. ret.context = this.context;
  7. return ret;
  8. };
  9. // #164, 能够回溯上一级 preObject
  10. end: function() {
  11. return this.preObject || this.constructor();
  12. }
  13. // #3063, 加入到新层 this.constructor()
  14. add: function( selector, context ) {
  15. return this.pushStack(
  16. // 去重排序
  17. jQuery.uniqueSort(
  18. // 合并到 this.get()
  19. jQuery.merge( this.get(), jQuery( selector, context ) )
  20. )
  21. );
  22. },
  23. addBack: function( selector ) {
  24. // add将把结果pushStack
  25. return this.add( selector == null ?
  26. this.preObject :
  27. // 可做一次过滤
  28. this.prevObject.filter( selector )
  29. );
  30. }

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


IE内存泄露

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

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

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

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

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

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

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

jQuery.data原理

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

  1. jQuery.extend({
  2. // #247, 构造一个基本不可能被占用的属性, 除去"."
  3. expando: "jQuery" + ( version + Math.random() ).replace(/\D/g, ""),
  4. // #507, 全局使用的 guid 号码
  5. guid: 1,
  6. // #4014, 缓存空间
  7. cache: {}
  8. });
  9. /* ---- 原理示意 ---- */
  10. var a = document.getElementById("id_a");
  11. // 每一个元素的 jQuery.expando 分到唯一的 guid 号码
  12. a[jQuery.expando] = a[jQuery.expando] || jQuery.guid++;
  13. // jQuery.cache 上元素的 guid 相应的位置为该对象的缓存空间
  14. 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提供的方式删除节点、绑定事件等

  1. function remove( elem, selector, keepData ) { // #6107, #6255为正式方法
  2. var node,
  3. elems = selector ?
  4. jQuery.filter( selector, elem ) : elem,
  5. i = 0;
  6. for ( ; ( node = elems[ i ] ) != null; i++ ) {
  7. if ( !keepData && node.nodeType === 1 ) {
  8. // 清除数据
  9. jQuery.cleanData( getAll( node ) );
  10. }
  11. if ( node.parentNode ) {
  12. if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
  13. setGlobalEval( getAll( node, "script" ) );
  14. }
  15. // 删除节点
  16. node.parentNode.removeChild( node );
  17. }
  18. }
  19. return elem;
  20. }

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结构

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

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

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

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

    1. acceptData( elem ) #3762,特性1。过滤
    2. dataAttr( elem, key, data ) #3780。特性2,data的值不存在则会訪问元素的attribute。返回data,且值会缓存到cache
    3. 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`。

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

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

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

[源代码]

  1. /* ---------------------------------- 1. 相关代码(扫一下) ---------------------------------- */
  2. jQuery.extend({
  3. // #247, 构造一个基本不可能被占用的属性, 除去"."
  4. expando: "jQuery" + ( version + Math.random() ).replace(/\D/g, ""),
  5. // #507, 全局使用的 guid 号码
  6. guid: 1,
  7. // #4014, 缓存空间
  8. cache: {},
  9. noData: {
  10. // 为 true 的不能够
  11. "applet ": true,
  12. "embed ": true,
  13. // ...but Flash objects (which have this classid) *can* handle expandos
  14. "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
  15. },
  16. });
  17. // #3748, support.deleteExpando
  18. ( function() {
  19. var div = document.createElement( "div" );
  20. // Support: IE<9
  21. support.deleteExpando = true;
  22. try {
  23. delete div.test;
  24. } catch ( e ) {
  25. support.deleteExpando = false;
  26. }
  27. // Null elements to avoid leaks in IE.
  28. div = null;
  29. } )();
  30. // #284, 可用于检測 elem 的公有 cache.data 是否已空
  31. jQuery.isEmptyObject = function( obj ) {
  32. var name;
  33. for ( name in obj ) {
  34. return false;
  35. }
  36. return true;
  37. };
  38. // #3814, 检測 elem 的私有 cache 缓存是否已空
  39. function isEmptyDataObject( obj ) {
  40. var name;
  41. for ( name in obj ) {
  42. // if the public data object is empty, the private is still empty
  43. if ( name === "data" && jQuery.isEmptyObject( obj[ name ] ) ) {
  44. continue;
  45. }
  46. if ( name !== "toJSON" ) {
  47. return false;
  48. }
  49. }
  50. return true;
  51. }
  52. /* ---------------------------------- 2. 钩子(瞅瞅) ---------------------------------- */
  53. // #3762
  54. var acceptData = function( elem ) {
  55. // 比对禁止列表 jQuery.noData, 为 true 必定不能
  56. var noData = jQuery.noData[ ( elem.nodeName + " " ).toLowerCase() ],
  57. // 普通对象都会按 nodeType = 1 处理,通过筛选
  58. nodeType = +elem.nodeType || 1;
  59. return nodeType !== 1 && nodeType !== 9 ?
  60. false :
  61. // noData不存在(黑名单) 或 存在且classid与noDta同样但不为true(白名单)
  62. !noData || noData !== true && elem.getAttribute( "classid" ) === noData;
  63. };
  64. function dataAttr( elem, key, data ) {
  65. // 正确姿势: dataAttr( elem, key, jQuery.data( elem )[ key ] );
  66. // data 不存在。则查找 HTML5 data-* attribute,并存入 jQuery.data( elem, key, data )
  67. // 返回 data
  68. if ( data === undefined && elem.nodeType === 1 ) {
  69. var name = "data-" + key.replace( /([A-Z])/g, "-$1" ).toLowerCase();
  70. data = elem.getAttribute( name );
  71. if ( typeof data === "string" ) {
  72. try {
  73. // "true" -> true , "false" -> false , "23" -> 23
  74. // "{ \"a\": 1}" -> {"a": 1}, "[1, '2']" -> [1, '2']
  75. // 或 data 本身
  76. data = data === "true" ?
  77. true :
  78. data === "false" ?
  79. false :
  80. data === "null" ? null :
  81. +data + "" === data ? +data :
  82. /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/.test( data ) ?
  83. jQuery.parseJSON( data ) :
  84. data;
  85. } catch ( e ) {}
  86. // 保存
  87. jQuery.data( elem, key, data );
  88. } else {
  89. data = undefined;
  90. }
  91. }
  92. return data;
  93. }
  94. // #356, 正则变量替换了,本身在 #80
  95. jQuery.camelCase = function( string ) {
  96. // -ms-abc -> msAbc , a-abc -> aAbc
  97. return string.replace( /^-ms-/, "ms-" ).replace( /-([\da-z])/gi, fcamelCase );
  98. };
  99. /* ---------------------------------- 3. 核心(关键) ---------------------------------- */
  100. // #3830, 加入缓存
  101. function internalData( elem, name, data, pvt /* true 为私,存cache;false 为公。存cache.data */ ) {
  102. // 钩子。黑白名单
  103. if ( !acceptData( elem ) ) {
  104. return;
  105. }
  106. var ret, thisCache,
  107. internalKey = jQuery.expando,
  108. // dom 和 object 区分对待
  109. isNode = elem.nodeType,
  110. // object 不缓存,存自身 jQuery.expando 属性上
  111. cache = isNode ?
  112. jQuery.cache : elem,
  113. // 没设置过说明 第一次
  114. id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
  115. // 1. 第一次仅仅能写,不能读。否则 return
  116. if ( ( !id || !cache[ id ] || ( !pvt && !cache[ id ].data ) ) &&
  117. data === undefined && typeof name === "string" ) {
  118. return;
  119. }
  120. // 2. 第一次写。先初始化 [ 属性、缓存对象 cache ]
  121. // dom -> jQuery.cache[ elem[ internalKey ] ]。object -> object[internalKey]
  122. if ( !id ) {
  123. // Only DOM nodes need a new unique ID for each element since their data
  124. // ends up in the global cache
  125. if ( isNode ) {
  126. id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
  127. } else {
  128. id = internalKey;
  129. }
  130. }
  131. if ( !cache[ id ] ) {
  132. cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
  133. }
  134. // 3. 写入与读取
  135. // write特例:支持 name 參数为 { "key" : value } 或 函数。存入数据
  136. if ( typeof name === "object" || typeof name === "function" ) {
  137. // 公私不同
  138. if ( pvt ) {
  139. cache[ id ] = jQuery.extend( cache[ id ], name );
  140. } else {
  141. cache[ id ].data = jQuery.extend( cache[ id ].data, name );
  142. }
  143. }
  144. thisCache = cache[ id ];
  145. // thisCache 依据pvt索引到了应该被赋值的对象
  146. if ( !pvt ) {
  147. if ( !thisCache.data ) {
  148. thisCache.data = {};
  149. }
  150. thisCache = thisCache.data;
  151. }
  152. // 写入,小驼峰为标准
  153. if ( data !== undefined ) {
  154. thisCache[ jQuery.camelCase( name ) ] = data;
  155. }
  156. // Check for both converted-to-camel and non-converted data property names
  157. // If a data property was specified
  158. if ( typeof name === "string" ) {
  159. // 这不是重点。也能够读非驼峰。正常使用并不会有
  160. ret = thisCache[ name ];
  161. if ( ret == null ) {
  162. // 读这里。不管读写,都要读一次
  163. ret = thisCache[ jQuery.camelCase( name ) ];
  164. }
  165. } else {
  166. // 无 name , 直接读 cache
  167. ret = thisCache;
  168. }
  169. return ret;
  170. }
  171. // #3922, 删除 公/私 缓存属性 或 缓存。
  172. // 删完属性若变空cache,将移去。会处理掉 elem 上 绑定的 event
  173. function internalRemoveData( elem, name, pvt ) {
  174. // 钩子,黑名单者,直接 return
  175. if ( !acceptData( elem ) ) {
  176. return;
  177. }
  178. var thisCache, i,
  179. isNode = elem.nodeType,
  180. cache = isNode ?
  181. jQuery.cache : elem,
  182. id = isNode ?
  183. elem[ jQuery.expando ] : jQuery.expando;
  184. // 缓存空间未初始化,return
  185. if ( !cache[ id ] ) {
  186. return;
  187. }
  188. // 1. name 存在,删除 name 属性值
  189. if ( name ) {
  190. thisCache = pvt ? cache[ id ] : cache[ id ].data;
  191. if ( thisCache ) {
  192. // 1.1 支持数组定义多属性,此处把字符串形式也转为数组[name]
  193. // next step: 统一迭代删除
  194. if ( !jQuery.isArray( name ) ) {
  195. // 这不是重点。也能够读非驼峰。
  196. 正常使用并不会有
  197. if ( name in thisCache ) {
  198. name = [ name ];
  199. } else {
  200. // 看这里,转换为小驼峰读
  201. name = jQuery.camelCase( name );
  202. if ( name in thisCache ) {
  203. name = [ name ];
  204. } else {
  205. // 能够字符串空格隔开多个,均变成小驼峰
  206. name = name.split( " " );
  207. }
  208. }
  209. } else {
  210. // If "name" is an array of keys...
  211. // When data is initially created, via ("key", "val") signature,
  212. // keys will be converted to camelCase.
  213. // Since there is no way to tell _how_ a key was added, remove
  214. // both plain key and camelCase key. #12786
  215. // This will only penalize the array argument path.
  216. name = name.concat( jQuery.map( name, jQuery.camelCase ) );
  217. }
  218. // 1.2 删
  219. i = name.length;
  220. while ( i-- ) {
  221. delete thisCache[ name[ i ] ];
  222. }
  223. // 1.3 假设 cache 删除后没空。结束 return
  224. // 假设空了, 与 name 不存在的情况一样直接删除 data
  225. if ( pvt ? !isEmptyDataObject( thisCache ) : !jQuery.isEmptyObject( thisCache ) ) {
  226. return;
  227. }
  228. }
  229. }
  230. // 2. 依据 pvt 推断,false 删除公有
  231. if ( !pvt ) {
  232. delete cache[ id ].data;
  233. // cache 还没空,能够闪了,return
  234. // cache 空了,合并到 pvt 为true,私有cache 删除
  235. if ( !isEmptyDataObject( cache[ id ] ) ) {
  236. return;
  237. }
  238. }
  239. // 3. pvt 为 true, 删除私有 cache
  240. // 3.1 为节点时。若cache[events]里还有事件,把 elem 绑定的事件函数删除
  241. if ( isNode ) {
  242. jQuery.cleanData( [ elem ], true );
  243. // 3.2 普通对象时
  244. } else if ( support.deleteExpando || cache != cache.window ) {
  245. // 能删则 delete
  246. delete cache[ id ];
  247. // 不能删, undefined
  248. } else {
  249. cache[ id ] = undefined;
  250. }
  251. }
  252. // #6192, 函数内包括 删除事件队列时删除elem相应type的绑定事件 的功能
  253. // cleanData 不仅被 internalRemoveData 内部调用,remove节点的时候也$().remove调用,因此支持了elems 数组
  254. jquery.cleanData = function( elems, /* internal */ forceAcceptData ) {
  255. var elem, type, id, data,
  256. i = 0,
  257. internalKey = jQuery.expando,
  258. cache = jQuery.cache,
  259. attributes = support.attributes,
  260. special = jQuery.event.special;
  261. // 支持 elems 数组迭代
  262. for ( ; ( elem = elems[ i ] ) != null; i++ ) {
  263. // 钩子
  264. if ( forceAcceptData || acceptData( elem ) ) {
  265. id = elem[ internalKey ];
  266. data = id && cache[ id ];
  267. if ( data ) {
  268. // 1. 存在事件队列
  269. if ( data.events ) {
  270. // 迭代,删除绑在 elem 上的触发函数
  271. for ( type in data.events ) {
  272. // 尽管绑定在data上的事件。都转换成标准的 eventType
  273. // 但标准的 eventtype 可能不被兼容
  274. // special.setup 钩子在绑定触发函数时会hack一次
  275. // 须要该方法找到绑定在elem上事件触发函数的真正类型并删除
  276. if ( special[ type ] ) {
  277. jQuery.event.remove( elem, type );
  278. // 普通情况,直接删除
  279. } else {
  280. jQuery.removeEvent( elem, type, data.handle );
  281. }
  282. }
  283. }
  284. // 2. 不存在(或已删去)事件队列
  285. if ( cache[ id ] ) {
  286. delete cache[ id ];
  287. // Support: IE<9
  288. // IE does not allow us to delete expando properties from nodes
  289. // IE creates expando attributes along with the property
  290. // IE does not have a removeAttribute function on Document nodes
  291. if ( !attributes && typeof elem.removeAttribute !== "undefined" ) {
  292. elem.removeAttribute( internalKey );
  293. } else {
  294. elem[ internalKey ] = undefined;
  295. }
  296. deletedIds.push( id );
  297. }
  298. }
  299. }
  300. }
  301. }
  302. /* ---------------------------------- 4. 外观(接口API) ---------------------------------- */
  303. // #4013
  304. jQuery.extend( {
  305. // cache、noData 上面已经提前写了
  306. cache: {},
  307. noData: {
  308. "applet ": true,
  309. "embed ": true,
  310. "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
  311. },
  312. // 直接从缓存 cache 查找推断。dom 与 object差别对待
  313. hasData: function( elem ) {
  314. elem = elem.nodeType ?
  315. jQuery.cache[ elem[ jQuery.expando ] ] : elem[ jQuery.expando ];
  316. return !!elem && !isEmptyDataObject( elem );
  317. },
  318. // 公用。pvt 无
  319. data: function( elem, name, data ) {
  320. return internalData( elem, name, data );
  321. },
  322. removeData: function( elem, name ) {
  323. return internalRemoveData( elem, name );
  324. },
  325. // 私用,pvt true
  326. _data: function( elem, name, data ) {
  327. return internalData( elem, name, data, true );
  328. },
  329. _removeData: function( elem, name ) {
  330. return internalRemoveData( elem, name, true );
  331. }
  332. } );
  333. // #4049,实例方法
  334. jQuery.fn.extend( {
  335. data: function( key, value ) {
  336. var i, name, data,
  337. elem = this[ 0 ],
  338. attrs = elem && elem.attributes;
  339. // Special expections of .data basically thwart jQuery.access,
  340. // so implement the relevant behavior ourselves
  341. // 1. key不存在。
  342. 获得 data缓存 全部值
  343. if ( key === undefined ) {
  344. if ( this.length ) {
  345. data = jQuery.data( elem );
  346. if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
  347. i = attrs.length;
  348. while ( i-- ) {
  349. // Support: IE11+
  350. // The attrs elements can be null (#14894)
  351. if ( attrs[ i ] ) {
  352. name = attrs[ i ].name;
  353. if ( name.indexOf( "data-" ) === 0 ) {
  354. name = jQuery.camelCase( name.slice( 5 ) );
  355. // 钩子,data[name]若无。则搜索data- attribute,并赋值给 data[ name ]
  356. dataAttr( elem, name, data[ name ] );
  357. }
  358. }
  359. }
  360. jQuery._data( elem, "parsedAttrs", true );
  361. }
  362. }
  363. return data;
  364. }
  365. // 2. key 是 "object"
  366. // internalData 已经支持了 "object" 參数。因此直接迭代
  367. if ( typeof key === "object" ) {
  368. return this.each( function() {
  369. jQuery.data( this, key );
  370. } );
  371. }
  372. return arguments.length > 1 ?
  373. // 3. 迭代写入 key -> value
  374. this.each( function() {
  375. jQuery.data( this, key, value );
  376. } ) :
  377. // 4. 读取 key 属性值, 没有则尝试读data- attribute,并赋值给 data[ name ]
  378. elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
  379. },
  380. removeData: function( key ) {
  381. return this.each( function() {
  382. jQuery.removeData( this, key );
  383. } );
  384. }
  385. } );

最后再说几点:

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

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

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

jQuery源代码解析(1)—— jq基础、data缓存系统的更多相关文章

  1. jQuery源代码解析(3)—— ready载入、queue队列

    ready.queue放在一块写,没有特殊的意思,仅仅是相对来说它俩可能源代码是最简单的了.ready是在dom载入完毕后.以最高速度触发,非常实用. queue是队列.比方动画的顺序触发就是通过默认 ...

  2. jQuery源代码 解析一 工具方法

    1. 外层沙箱以及命名空间$ 几乎稍微有点经验前端人员都这么做,为了避免声明了一些全局变量而污染,把代码放在一个"沙箱执行",然后在暴露出命名空间(可以为API,函数,对象): 2 ...

  3. convnet源代码解析(一):基础准备

    Jeremy Lin ConvNet是一个基于GPU实现的卷积神经网络开源码(C++11).是由多伦多大学的Geoffrey Hinton深度学习团队编写的,它的最初版本号是Hinton的学生Alex ...

  4. jQuery源代码阅读之三——jQuery实例方法和属性

    jQuery实例方法及属性相关的代码结构如下 jQuery.fn=jQuery.prototype={ jQuery:core_version, constructor:jQuery, selecto ...

  5. Spark MLlib LDA 源代码解析

    1.Spark MLlib LDA源代码解析 http://blog.csdn.net/sunbow0 Spark MLlib LDA 应该算是比較难理解的,当中涉及到大量的概率与统计的相关知识,并且 ...

  6. jQuery源代码学习之六——jQuery数据缓存Data

    一.jQuery数据缓存基本原理 jQuery数据缓存就两个全局Data对象,data_user以及data_priv; 这两个对象分别用于缓存用户自定义数据和内部数据: 以data_user为例,所 ...

  7. jQuery源码解读 - 数据缓存系统:jQuery.data

    jQuery在1.2后引入jQuery.data(数据缓存系统),主要的作用是让一组自定义的数据可以DOM元素相关联——浅显的说:就是让一个对象和一组数据一对一的关联. 一组和Element相关的数据 ...

  8. jQuery.data的是jQuery的数据缓存系统

    jQuery.Data源码 jQuery.data的是jQuery的数据缓存系统 jQuery.data的是jQuery的数据缓存系统.它的主要作用就是为普通对象或者DOM元素添加数据. 1 内部存储 ...

  9. Android源代码解析之(七)--&gt;LruCache缓存类

    转载请标明出处:一片枫叶的专栏 android开发过程中常常会用到缓存.如今主流的app中图片等资源的缓存策略通常是分两级.一个是内存级别的缓存,一个是磁盘级别的缓存. 作为android系统的维护者 ...

随机推荐

  1. R in action读书笔记(4)-第六章:基本图形(下)

    6.3直方图 hist() 其中的x是一个由数据值组成的数值向量.参数freq=FALSE表示根据概率密度而不是频数绘制图形.参数breaks用于控制组的数量.在定义直方图中的单元时,默认将生成等距切 ...

  2. vs2015 qt5.8新添加文件时出现“无法找到源文件ui.xxx.h”

    转载请注明出处:http://www.cnblogs.com/dachen408/p/7147135.html vs2015 qt5.8新添加文件时出现“无法找到源文件ui.xxx.h” 暂时解决版本 ...

  3. JavaScript——分页

  4. Farseer.net轻量级开源框架 中级篇:UrlRewriter 地址重写

    导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 中级篇: Cookies.Session.Request 下一篇:Farseer.net轻量 ...

  5. tomcat不打印日志

    commons-logging.jar导入这个包到tomcat  lib下 2.修改tomcat的bin目录下面的catalina.bat文件   只需修改:set CLASSPATH=%CLASSP ...

  6. redisd的非持久化配置

    如何关闭redis持久化?我的需求是只把redis当作缓存来用,所以持久化到硬盘对我的需求来说没有意义. 修改redis配置文件,redis.conf 第115行左右. 1.注释掉原来的持久化规则 # ...

  7. 谈谈JVM垃圾回收机制及垃圾回收算法

    一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理.由于有个垃圾回收机制 ...

  8. IDEA基本使用及配置(1)

    前言:现在IDEA用的人很多,我以前都是用Eclipse的,都说这个IDE比较智能.好用,于是学习一下. IDEA与Eclipse目录结构对比: IDEA中的Project相当于Eclispe中的wo ...

  9. web前端开发——css

    一.css介绍 1.css是什么? Cascading Style Sheets缩写,层叠样式表.样式定义如何显示HTML元素,样式通常又会存在于样式表中. 2.为什么需要css? 使HTML页面变得 ...

  10. IIS 注册.NET Framework 4.0 命令

    cmd执行以下命令 32位Windows:C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -i 64位Windows:C ...