jQuery自定義了jQuery.extend()和jQuery.fn.extend()方法.其中jQuery.extend()方法能夠創建全局函數或者選擇器,而jQuery.fn.extend()方法能夠創建jQuery對象方法.
這兩個方法用的是相同的代碼,一個用於給jQuery對象或者普通對象合並屬性和方法一個是針對jQuery對象的實例,對於基本用法舉幾個例子:
html代碼如下:
代碼如下:
下面寫js裡面的用法:
合並兩個普通對象
代碼如下:
//給兩個普通對象合並屬性
var obj1={name:'Tom',age:22};
var obj2={name:'Jack',height:180};
console.log($.extend(obj1,obj2)); //Object {name: "Jack", age: 22, height: 180}
給jQuery對象添加屬性或者方法
代碼如下:
$.extend({hehe:function(){alert('hehe');}});
$.hehe(); //alert('hehe')
這個用法很重要,是jQuery內部添加實例屬性和方法以及原型屬性和方法的實現方法也是編寫jQuery插件的方法,下面是jQuery1.7.1中使用extend方法擴展自己的方法和屬性
代碼如下:
jQuery.extend({
noConflict: function( deep ) {
if ( window.$ === jQuery ) {
window.$ = _$;
}
if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
return jQuery;
},
// Is the DOM ready to be used? Set to true once it occurs.
isReady: false,
// A counter to track how many items to wait for before
// the ready event fires. See #6781
readyWait: 1,
.....
在這個例子中只傳入了一個對象參數,那麼默認就把this當做待合並修改的對象
給jQuery對象實例添加屬性或者方法
代碼如下:
//針對jQuery實例擴展合並
console.log($('img').extend({'title':'img'}));//[img, img#img.img, prevObject: jQuery.fn.jQuery.init[1], context: document, selector: "img", title: "img", constructor: function…]
只合並不修改待合並對象
代碼如下:
var obj1={name:'Tom',age:22};
var obj2={name:'Jack',height:180};
console.log($.extend(obj1,obj2)); //Object {name: "Jack", age: 22, height: 180}
console.log(obj1); //Object {name: "Jack", age: 22, height: 180}
默認情況下,待合並對象跟返回結果一樣是被修改了的,如果僅僅想得到一個合並後的對象又不想破壞任何一個原來的對象可以使用此方法
代碼如下:
var obj1={name:'Tom',age:22};
var obj2={name:'Jack',height:180};
var empty={};
console.log($.extend(empty,obj1,obj2)); //Object {name: "Jack", age: 22, height: 180}
console.log(obj1); //Object {name: "Tom", age: 22}
使用則遞歸合並或者叫深度拷貝
代碼如下:
var obj1={name:'Tom',love:{drink:'milk',eat:'bread'}};
var obj2={name:'Jack',love:{drink:'water',sport:'football'}};
console.log(($.extend(false,obj1,obj2)).love); //Object {drink: "water", sport: "football"}
console.log(($.extend(true,obj1,obj2)).love); //Object {drink: "water", eat: "bread", sport: "football"}
詳細的使用方法可以看參考手冊http://www.w3cschool.cc/manual/jquery/
下面來分析下1.7.1源碼中是怎麼實現的:
代碼如下:
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
...
}
首先是定義了一組變量,因為參數個數不確定所以就直接調用arguments對象訪問傳遞的參數
變量 options:指向某個源對象。
‰ ‰ 變量 name:表示某個源對象的某個屬性名。
‰ ‰ 變量 src:表示目標對象的某個屬性的原始值。
‰ ‰ 變量 copy:表示某個源對象的某個屬性的值。
‰ ‰ 變量 copyIsArray:指示變量 copy 是否是數組。
‰ ‰ 變量 clone:表示深度復制時原始值的修正值。
‰ ‰ 變量 target:指向目標對象。
‰ ‰ 變量 i:表示源對象的起始下標。
‰ ‰ 變量 length:表示參數的個數,用於修正變量 target。
‰ ‰ 變量 deep:指示是否執行深度復制,默認為 false。
為了更好地了解代碼實現這裡以上面舉的一個例子作為演示觀察源代碼執行情況
代碼如下:
var obj1={name:'Tom',love:{drink:'milk',eat:'bread'}};
var obj2={name:'Jack',love:{drink:'water',sport:'football'}};
$.extend(true,obj1,obj2)
源碼分析
代碼如下:
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
判斷是不是深度復制,如果第一個參數是布爾值那麼就把第一個參數的值給deep,然後把第二個參數作為目標對象,如果第二個參數不存在就賦值為一個空對象,把源對象的下標改為2,在這個例子裡面 是走這裡的因為第一個參數是ture然後把deep變成了true ,target被修正成了第二個參數也即是obj1,源對象的起始下標為2就是從第三個開始作為源對象也就是本例中的obj2
代碼如下:
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
這裡對target又進一步進行了處理對於非對象和函數的數據類型而言增加自定義屬性是無效的比如字符串自能調用自帶的方法和屬性
代碼如下:
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
如果length屬性等於i的值那就表示沒有目標對象存在,正常情況下length應該是大於i的值的 ,那麼這個時候就把this作為目標對象把i值減一實現length值大於i值(比i大1)
這個就是jQuery給自己擴展屬性的方法的實現原理,只要不傳入目標對象就可以啦
兩種可能的情況:$.extend(obj) 或者 $.extend(false/true,obj);
代碼如下:
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
這個部分就是此方法的核心了,從arguements對象的第i個下標值開始循環操作首先過濾掉源對象是null或者是undefined的情況可以看到其實
源對象不一定真的就是對像,也可以是其他類型的值比如字符串比如這樣寫:
代碼如下:
console.log($.extend({'name':'tom'},'aa')); //Object {0: "a", 1: "a", name: "tom"}
是不是感覺很奇怪啊?究竟是怎麼實現的呢?下面接著看
過濾完之後開始進行for循環 src保存的是目標對象的某個鍵的值,copy屬性保存的源對象的某個鍵的值,這兩個鍵都是一樣的
代碼如下:
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
如果源對象的某個屬性值就是目標對象可能會造成死循環導致程序崩潰所以這裡做了一個限制讓其跳過此次循環例如:
代碼如下:
var o = {};
o.n1 = o;
$.extend( true, o, { n2: o } );
// 拋出異常:
// Uncaught RangeError: Maximum call stack size exceeded
但是這樣做也會冤枉一些正常的情況比如:
代碼如下:
var obj1={a:'a'}
var obj2={a:obj1};
console.log($.extend(obj1,obj2)); //Object {a: "a"}
這種情況也是滿足源對象值等於目標對象的但是結果發現obj1的a的屬性值並沒有被修改,就是因為執行了continue,下面把源碼的這段話注釋掉在執行
代碼如下:
Object {a: Object}
這個時候就是正常被修改了個人感覺這個地方需要改進;
接著就是一個if判斷就是區分是不是進行深度復制的先不看深度復制的先看一般的
代碼如下:
target[ name ] = copy;
很簡單就是只要copy有值就直接復制給目標對象,目標對象有的就修改沒有就增加,這樣就實現了合並啦。
for循環之後在把新的目標對象返回,所以目標對象最後是被修改的,而且結果和返回的結果是一樣的。
代碼如下:
// Return the modified object
return target;
};
下面再來說說深度復制了怎麼去處理
首先保證deep是true,copy有值並且是對象或者數組(如果不是對象和數組深度復制也就無從談起)然後再分數組和對象來處理,先來看數組的情況:
代碼如下:
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
如果是數組copyIsArray的值為真然後走裡面的 把值改成false ,針對當前循環的源對象屬性,目標對象可能有也可能沒有,有的話判斷一下是不是數組是的話就是原來的數組不變不是的話就讓它變成一個數組,因為既然源對象的當前屬性是數組最後目標元素也必須是數組。不是數組就是對象把目標對象當前屬性改成對象。
代碼如下:
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
然後把源對象的當前屬性值(是數組或對象)和已經被改造過的目標對象的當前屬性進行遞歸合並最後返回的新的數組或者對象賦值給目標對象,最終實現了深度復制。
但是這裡面還有一個比較奇怪的現象,比如這樣操作:
代碼如下:
console.log($.extend({a:1},'aa')); //Object {0: "a", 1: "a", a: 1}
原來源對象不一定真的是對象e而且居然可以把字符串拆開跟目標對象合並,原來for...in循環是操作字符串的
代碼如下:
var str='aa';
for(var name in str){
console.log(name);
console.log(str[name])
}
這樣也是可以的,會把字符串拆開按數字下標讀取,但是在源碼中
代碼如下:
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) )
是有數組和對象限制的,那麼深度復制的時候是不是就沒有效果了呢?
經過我測試深度復制也是可以的,因為在源碼裡面copy的值竟然變成了匿名函數函數
alert(jQuery.isPlainObject(copy)); //true
至於為什麼是函數筆者還沒搞清楚留待以後解決吧!