天天看點

jQuery技術内幕:深入解析jQuery架構設計與實作原理. 2.6 jQuery.extend()、jQuery.fn.extend()

<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" &amp;&amp;

!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 &lt; 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 &amp;&amp; copy

&amp;&amp; ( jquery.isplainobject(copy) || (copyisarray = jquery.isarray(copy))

) ) {

365                     if ( copyisarray ) {

366                         copyisarray = false;

367                         clone = src &amp;&amp;

jquery.isarray(src) ? src : [];

368

369                     } else {

370                         clone = src &amp;&amp;

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

繼續閱讀