<b>2.6 jquery.extend()、jquery.fn.extend()</b>
<b>2.6.1 如何使用</b>
方法jquery.extend()和jquery.fn.extend()用于合并两个或多个对象的属性到第一个对象,它们的语法如下:
jquery.extend( [deep], target, object1 [,
objectn] )
jquery.fn.extend( [deep], target, object1
[, objectn] )
其中,参数deep是可选的布尔值,表示是否进行深度合并(即递归合并)。合并行为默认是不递归的,如果第一个参数的属性本身是一个对象或数组,它会被第二个或后面的其他参数的同名属性完全覆盖。如果为true,表示进行深度合并,合并过程是递归的。
参数target是目标对象;参数object1和objectn是源对象,包含了待合并的属性。如果提供了两个或更多的对象,所有源对象的属性将会合并到目标对象;如果仅提供一个对象,意味着参数target被忽略,jquery或jquery.fn被当作目标对象,通过这种方式可以在jquery或jquery.fn上添加新的属性和方法,jquery的其他模块大都是这么实现的。
<b>方法jquery.extend()和jquery.fn.extend()常用于编写插件和处理函数的参数。</b>
<b>2.6.2 源码分析</b>
方法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, clone,
326
target = arguments[0] || {},
327
i = 1,
328
length = arguments.length,
329
deep = false;
330
第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
339
// handle case when target is a string or something (possible in deep
copy)
340
if ( typeof target !== "object" &&
!jquery.isfunction(target) ) {
341
target = {};
342
343
344
// extend jquery itself if only one argument is passed
345
if ( length === i ) {
346
target = this;
347
--i;
348
349
第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
358 // prevent never-ending loop
359 if ( target === copy ) {
360 continue;
361 }
362
363 // recurse if we're merging
plain objects or arrays
364 if ( deep && copy
&& ( jquery.isplainobject(copy) || (copyisarray = jquery.isarray(copy))
) ) {
365 if ( copyisarray ) {
366 copyisarray = false;
367 clone = src &&
jquery.isarray(src) ? src : [];
368
369 } else {
370 clone = src &&
jquery.isplainobject(src) ? src : {};
371 }
372
373 // never move original
objects, clone them
374 target[ name ] =
jquery.extend( deep, clone, copy );
375
376 // don't bring in undefined
values
377 } else if ( copy !== undefined
) {
378 target[ name ] = copy;
379 }
380 }
381
382
383
第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
方法jquery.isplainobject()将在2.8.2节介绍和分析。
第374行:先把复制值copy递归合并到原始值副本clone中,然后覆盖目标对象的同名属性。
第376~379行:如果不是深度合并,并且复制值copy不是undefined,则直接覆盖目标对象的同名属性。