-
- 苏州网站建设带你一起玩转你不知道的jQuery
苏州网站建设 苏州网站制作 苏州网页设计 方法 jQuery.extend() 和 jQuery.fn.extend() 执行的关键步骤如下所示: 1)修正参数 deep、target、源对象的起始下标。 2)逐个遍历源对象:
a. 遍历源对象的属性。
b. 覆盖目标对象的同名属性;如果是深度合并,则先递归调用 jQuery.extend()。 下面分析方法 jQuery.extend() 和 jQuery.fn.extend() 的源码。
1. 定义 jQuery.extend() 和 jQuery.fn.extend()
相关代码如下所示:
324 jQuery.extend = jQuery.fn.extend = function() {
第 324 行:因为参数的个数是不确定的,可以有任意多个,所以没有列出可接受的参数。
2. 定义局部变量
相关代码如下所示:
325 var options, name, src, copy, copyIsArray,
326 clone, target = arguments[0] || {},
327 i = 1,
328 length = arguments.length,
329 deep = false;
第 325 ~ 329 行:定义一组局部变量,它们的含义和用途如下:
变量 options:指向某个源对象。
变量 name:表示某个源对象的某个属性名。
变量 src:表示目标对象的某个属性的原始值。
变量 copy:表示某个源对象的某个属性的值。
变量 copyIsArray:指示变量 copy 是否是数组。
变量 clone:表示深度复制时原始值的修正值。
变量 target:指向目标对象。
变量 i:表示源对象的起始下标。
变量 length:表示参数的个数,用于修正变量 target。
变量 deep:指示是否执行深度复制,默认为 false。
3. 修正目标对象 target、源对象起始下标 i
相关代码如下所示:331 // Handle a deep copy situation
332 if ( typeof target === "boolean" ) {
333 deep = target;
334 target = arguments[1] || {};
335 // skip the boolean and the target
336 i = 2;
337 }
338 // Handle case when target is a string or something (possible in deep copy)
339 if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
340 target = {};
341 // extend jQuery itself if only one argument is passed
342 if ( length === i ) {
343 target = this;
344 --i;
345 }
第 331~337 行:如果第一个参数是布尔值,则修正第一个参数为 deep,修正第二个参数 为目标对象 target,并且期望源对象从第三个元素开始。
变量 i 的初始值为 1,表示期望源对象从第 2 个元素开始;当第一个参数为布尔型时, 变量 i 变为 2,表示期望源对象从第 3 个元素开始。
第 339 ~ 342 行:如果目标对象 target 不是对象、不是函数,而是一个字符串或其他的 基本类型,则统一替换为空对象 {},因为在基本类型上设置非原生属性是无效的。例如:
var s = 'hi';
s.test = 'hello';
console.log( s.test ); // 打印 undefined
第 344 ~ 348 行:变量 i 表示源对象开始的下标,变量 length 表示参数个数,如果二者相等,表示期望的源对象没有传入,则把 jQuery 或 jQuery.fn 作为目标对象,并且把源对象 的开始下标减一,从而使得传入的对象被当作源对象。变量 length 等于 i 可能有两种情况:
extend( object ),只传入了一个参数。
extend( deep, object ),传入了两个参数,第一个参数是布尔值。
4. 逐个遍历源对象
相关代码如下所示:
350 for ( ; i < length; i++ ) {
第 350 行:循环变量 i 表示源对象开始的下标,很巧妙的用法。
-
(1)遍历源对象的属性
下面的代码用于遍历源对象的属性:
351 // Only deal with non-null/undefined values
352 if ( (options = arguments[ i ]) != null ) {
353 // Extend the base object
354 for ( name in options ) {
第 352 行:arguments 是一个类似数组的对象,包含了传入的参数,可以通过整型下标 访问指定位置的参数。这行代码把获取源对象和对源对象的判断合并为一条语句,只有源对 象不是 null、undefined 时才会继续执行。
第 353 行:开始遍历单个源对象的属性。
-
(2)覆盖目标对象的同名属性
相关代码如下所示:
355 src = target[ name ];
356 copy = options[ name ];
357 // Prevent never-ending loop
358 if(target===copy){
359 continue;
360 }
361 // Recurse if we're merging plain objects or arrays
362 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
363 if ( copyIsArray ) {
364 copyIsArray = false;
365 clone = src && jQuery.isArray(src) ? src : [];
366 }else{
367 clone = src && jQuery.isPlainObject(src) ? src : {}; }
368 // Never move original objects, clone them
369 target[ name ] = jQuery.extend( deep, clone, copy );
370 // Don't bring in undefined values } else if ( copy !== undefined ) {
371 target[ name ] = copy;
第 355 ~ 361 行:变量 src 是原始值,变量 copy 是复制值。如果复制值 copy 与目标对 象 target 相等,为了避免深度遍历时死循环,因此不会覆盖目标对象的同名属性。如果注释 掉第 360 行,下面的代码会抛出堆栈溢出异常:
var o = {};
o.n1 = o;
$.extend( true, o, { n2: o } );
// 抛出异常:
// Uncaught RangeError: Maximum call stack size exceeded
注意,判断 target === copy 时使用的是“ ===”,强制不做类型转换;如果使用“ ==”, 则可能因自动类型转换而导致错误 。
第 364 ~ 374 行:如果是深度合并,且复制值 copy 是普通 JavaScript 对象或数组,则递 归合并。
第 365 ~ 371 行:复制值 copy 是数组时,如果原始值 src 不是数组,则修正为空数组; 复制值 copy 是普通 JavaScript 对象时,如果原始值 src 不是普通 JavaScript 对象,则修正为 空对象 {}。把原始值 src 或修正后的值赋值给原始值副本 clone。
通过调用方法 jQuery.isPlainObject( copy ) 判断复制值 copy 是否是“纯粹”的 JavaScript 对象,只有通过对象直接量 {} 或 new Object() 创建的对象,才会返回 true。例如:
jQuery.isPlainObject( { hello: 'world' } ); // true
jQuery.isPlainObject( new Object() ); // true
jQuery.isPlainObject( new Object(1) ); // true
.selector、.jquery、.length、.size()
属性 selector 用于记录 jQuery 查找和过滤 DOM 元素时的选择器表达式,但不一定是可执行的选择器表达式,该属性更多的是为了方便调试。下面是一些示例:
$('div').find('p').selector // "div p"
$('div p').selector // "div p"
$('div').first().selector // "div.slice(0,1)"
属性 jquery 表示正在使用的 jQuery 版本号。
属性 .length 表示当前 jQuery 对象中元素的个数。
方法 .size() 返回当前 jQuery 对象中元素的个数。方法 .size() 在功能上等价于属性 .length,
但应该优先使用属性 .length,因为它没有函数调用开销。
属性 .selector、.jquery、.length、.size() 的相关代码如下所示:
209 // Start with an empty selector
210 selector: "",
212 // The current version of jQuery being used
213 jquery: "1.7.1",
215 // The default length of a jQuery object is 0
216 length: 0,
218 // The number of elements contained in the matched element set
219 size: function() {
220 return this.length;
221 },
.toArray()、.get( [index] )
1. .toArray()
方法 .toArray() 将当前 jQuery 对象转换为真正的数组,转换后的数组包含了所有元素。 方法 .toArray() 的实现巧妙地借用了数组的方法 slice(),相关的代码如下所示:
86 // Save a reference to some core methods
87 toString = Object.prototype.toString,
88 hasOwn = Object.prototype.hasOwnProperty,
89 push = Array.prototype.push,
90 slice = Array.prototype.slice,
91 trim = String.prototype.trim,
92 indexOf = Array.prototype.indexOf,
223 toArray: function() {
224 return slice.call( this, 0 );
225 }
第 86 ~ 92 行: 连 同 slice() 一 起 声 明 的 还 有 toString()、hasOwn()、trim()、indexOf(), 这里通过声明对这些核心方法的引用,使得在 jQuery 代码中可以借用这些核心方法的功能, 执行时可通过方法 call() 和 apply() 指定方法执行的环境,即关键字 this 所引用的对象。这种“借鸡下蛋”的技巧非常值得借鉴。
数组方法 slice() 返回数组的一部分,语法如下
array.slice(start, end)
// 参数 start 表示数组片段开始处的下标。如果是负数,它声明从数组末尾开始算起的位置
// 参数end 表示数组片段结束处的后一个元素的下标。如果没有指定这个参数,切分的数组包含从 start
开始到数组结束的所有元素。如果这个参数是负数,它声明的是从数组尾部开始算起的元素
2. .get( [index] )
方法 .get( [index] ) 返回当前 jQuery 对象中指定位置的元素或包含了全部元素的数组。 如果没有传入参数,则调用 .toArray() 返回包含了所有元素的数组;如果指定了参数 index, 则返回一个单独的元素;参数 index 从 0 开始计算,并且支持负数,负数表示从元素集合末尾开始计算。相关代码如下所示:
227 // Get the Nth element in the matched element set OR
228 // Get the whole matched element set as a clean array
229 get: function( num ) {
230 return num == null ?
231 // Return a 'clean' array
232 this.toArray() :
233 // Return just the object
234 ( num < 0 ? this[ this.length + num ] : this[ num ] ); },
第 236 行:先判断 num 是否小于 0,如果小于 0,则用 length + num 重新计算下标,然 后使用数组访问操作符([])获取指定位置的元素,这是支持下标为负数的一个小技巧;如果num 大于等于 0,则直接返回指定位置的元素。
.each( function(index, Element) )、jQuery.each( collection, callback (indexInArray, valueOfElement) )
方法 .each() 遍历当前 jQuery 对象,并在每个元素上执行回调函数。每当回调函数执行时,会 传递当前循环次数作为参数,循环次数从 0 开始计数;更重要的是,回调函数是在当前元素为上 下文的语境中触发的,即关键字 this 总是指向当前元素;在回调函数中返回 false 可以终止遍历。
方法 .each() 内部通过简单的调用静态方法 jQuery.each() 实现,相关代码如下所示:
267 // Execute a callback for every element in the matched set.
268 // (You can seed the arguments with an array of args, but this is
269 // only used internally.)
270 each: function( callback, args ) {
271 return jQuery.each( this, callback, args );
272 }
2. jQuery.each( collection, callback(indexInArray, valueOfElement) )
静态方法 jQuery.each() 是一个通用的遍历迭代方法,用于无缝地遍历对象和数组。对于 数组和含有 length 属性的类数组对象(如函数参数对象 arguments),该方法通过下标遍历, 从 0 到 length-1 ;对于其他对象则通过属性名遍历(for-in)。在遍历过程中,如果回调函数 返回 false,则结束遍历。
定义方法 jQuery.each( object, callback, args ),它接受 3 个参数。
-
参数 object:待遍历的对象或数组。
-
参数 callback:回调函数,会在数组的每个元素或对象的每个属性上执行。
-
参数 args:传给回调函数 callback 的参数数组,可选。如果没有传入参数 args,则执行回调函数时会传入两个参数(下标或属性名,对应的元素或属性值)如果传入了参数 args,则只把该参数传给回调函数。
-
- 上一篇: 到底怎么回事?为什么电脑存储盘没有A、B盘,而是从C盘开始划分的?
- 下一篇: 跳一跳只是个开胃菜,微信小程序是要准备开足马力爆发吗?
- 苏州网站建设带你一起玩转你不知道的jQuery