<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,則直接覆寫目标對象的同名屬性。