闲话

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的支持,#10991if ( typeof define === "function" && define.amd ) {define( "jquery", [], function() {return jQuery;} );}// 保存之前的 window.$   window.jQueryvar _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将把结果pushStackreturn 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 -> funa.onclick = function(e) {};// fun -> a -> dom , 解除对dom元素的引用, fun -> a -X- doma = 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,仿佛回到原点,但是数据还在,比之前更不如。解决方法其实很简单,就是移除节点时调用 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缓存系统并没有这样使用。原因也很简单,基础的共性部分支持不同不支持映射,如上面的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<9support.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 emptyif ( 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 )// 返回 dataif ( 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 -> aAbcreturn 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. 第一次只能写,不能读。否则 returnif ( ( !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 cacheif ( 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 specifiedif ( typeof name === "string" ) {// 这不是重点,也可以读非驼峰。正常使用并不会有ret = thisCache[ name ];if ( ret == null ) {// 读这里,无论读写,都要读一次ret = thisCache[ jQuery.camelCase( name ) ];}} else {// 无 name , 直接读 cacheret = thisCache;}return ret;
}// #3922, 删除 公/私 缓存属性 或 缓存。
// 删完属性若变空cache,将移去。会处理掉 elem 上 绑定的 event
function internalRemoveData( elem, name, pvt ) {// 钩子,黑名单者,直接 returnif ( !acceptData( elem ) ) {return;}var thisCache, i,isNode = elem.nodeType,cache = isNode ? jQuery.cache : elem,id = isNode ? elem[ jQuery.expando ] : jQuery.expando;// 缓存空间未初始化,returnif ( !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 不存在的情况一样直接删除 dataif ( 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 ) {// 能删则 deletedelete 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 nodesif ( !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 -> valuethis.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才加载完呀,才会产生这个需要。

jQuery源码解析(1)—— jq基础、data缓存系统相关推荐

  1. jquery源码解析:jQuery数据缓存机制详解2

    上一课主要讲了jQuery中的缓存机制Data构造方法的源码解析,这一课主要讲jQuery是如何利用Data对象实现有关缓存机制的静态方法和实例方法的.我们接下来,来看这几个静态方法和实例方法的源码解 ...

  2. jquery源码解析:代码结构分析

    本系列是针对jquery2.0.3版本进行的讲解.此版本不支持IE8及以下版本. (function(){ (21, 94)     定义了一些变量和函数,   jQuery = function() ...

  3. jQuery源码解析(架构与依赖模块)

    jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架.它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, FF 1. ...

  4. jQuery源码解析(架构与依赖模块)第一章 理解架构

    1-1 jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架.它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, F ...

  5. JQuery 源码解析资料

    2019独角兽企业重金招聘Python工程师标准>>> jQuery源码分析系列目录 jQuery源码解读-理解架构 jQuery源码解析(架构与依赖模块) jQuery v1.10 ...

  6. Jquery源码中的Javascript基础知识(三)

    这篇主要说一下在源码中jquery对象是怎样设计实现的,下面是相关代码的简化版本: 1 (function( window, undefined ) { 2 // code 定义变量 3 jQuery ...

  7. jQuery源码解析之on事件绑定

    本文采用的jQuery源码为jquery-3.2.1.js jquery的on方法用来在选定的元素上绑定一个或多个事件处理函数. 当参数selector存在时,通常会用来对已经存在的元素或将来即将添加 ...

  8. jQuery 源码解析一:jQuery 类库整体架构设计解析

    如果是做 web 的话,相信都要对 Dom 进行增删查改,那大家都或多或少接触到过 jQuery 类库,其最大特色就是强大的选择器,让开发者脱离原生 JS 一大堆 getElementById.get ...

  9. 浅谈jquery源码解析

    本文主要是针对jquery  v3.x版本来描述的,将从以下几个方面谈谈我对jquery的认识, 总体架构 $与$.fn jQuery.fn.init  (重要) jQuery.extend  与jQ ...

最新文章

  1. JavaScript的语言组成
  2. 47万实例数据集,智源联合旷视发布2020 CrowdHuman人体检测大赛
  3. 市场与需求带动 向智能安放转型成大势所趋
  4. 易语言通过服务器发送文件,易语言服务器与客户端发送文件
  5. 又收获一位副总裁?传暴风TV CEO刘耀平已加盟小米电视
  6. 转帖:DotNet 资源大全中文版
  7. 【To Understand! 回文串6 KMP算法】LeetCode 214. Shortest Palindrome
  8. Android界面绘制流程--------How Android Draws Views
  9. 如何修改MySQL数据库中表和表中字段的编码方式
  10. centos下修改mysql默认端口
  11. 【UE4笔记】Collision碰撞
  12. 外贸软件进口供应链管理解决方案
  13. 如鹏网.Net三层架构 第四章代码生成器
  14. 简易智能自动问答机器人
  15. 一篇文章让你认识什么是saas模式
  16. 机器学习之用Hog+Svm人脸检测、交通标志和字符识别等(初学者)
  17. 扫描文件存电子版方法
  18. 给网页添加动态视频背景 html+css
  19. 答题卡识别任务--opencv python(附代码)
  20. 硬件加密算法HITAG2流程分析

热门文章

  1. GridMask:SOTA 数据增广方法,显著改进分类、检测、分割效果
  2. 3详细参数_大疆精灵3值得入手吗?最详细的实测体验,各种参数应有尽有!
  3. Transformer组件很重要Attention is all you need
  4. 主成分分析降维(MNIST数据集)
  5. python工程师工资状况_【python工程师工资|python工程师待遇怎么样】-看准网
  6. java outer关键字_java中的关键字
  7. Soft NMS论文笔记
  8. 2019上半年系统集成项目管理工程师下午案例分析真题与答案解析
  9. 软考网络管理员学习笔记1之第一章计算机硬件基础
  10. 1900-01-01t00:00:00+08:00 java_日期格式转换 java 2016-09-03T00:00:00.000+08:00