jQuery有個(gè).extend()方法來(lái)擴(kuò)展一個(gè)類或數(shù)組,語(yǔ)法如下:
jQuery.extend( [deep ], target, object1 [, objectN ] )
第一個(gè)可選參數(shù)deep讓我們選擇是否使用深克隆,默認(rèn)為否。
什么是深克隆、什么是淺克隆呢?
JS中的基本類型(undefined, null, Number, String, Boolean)是按值傳遞的,引用類型(array, object, function)是按址傳遞的。
淺克隆,就是常見(jiàn)的賦值(a = b)或者參數(shù)傳遞,基本類型按值傳遞,引用類型按址傳遞。
深克隆,基本類型和引用類型都按值傳遞,也就是說(shuō),所有的元素都完全克隆,與原來(lái)的元素互相獨(dú)立,之后修改其中的一個(gè)元素不會(huì)影響到另外一個(gè)。
舉個(gè)例子:
var obj = {
a: [1, 2, 3],
b: {b1: 1, b2: 2},
c: 'c'
};
var obj1 = obj; // 淺克隆,引用類型按址傳遞
var obj2 = Object.assign({}, obj); // 淺克隆
var obj3 = JSON.parse(JSON.stringify(obj)); // 深克隆
obj.c = 'C'; // 改變obj
console.log(obj1.c, obj2.c, obj3.c) // C c c
console.log(obj1.a === obj.a) // true, obj.a 和 obj1.a 引用的是同一塊地址
console.log(obj2.a === obj.a) // true
console.log(obj3.a === obj.a) // false
obj.a.push(4);
console.log(obj1.a, obj2.a, obj3.a); // [ 1, 2, 3, 4 ] [ 1, 2, 3, 4 ] [ 1, 2, 3 ]
JSON.parse(JSON.stringify(obj))有點(diǎn)奇技淫巧的意思,但是它有個(gè)小缺陷,就是只能克隆JSON對(duì)象,如果對(duì)象中包含函數(shù),函數(shù)會(huì)被忽略。
var obj = {
a: [1, 2, 3],
b: {b1: 1, b2: 2},
c: function () {}
};
var obj1 = JSON.parse(JSON.stringify(obj));
console.log(obj1) // { a: [ 1, 2, 3 ], b: { b1: 1, b2: 2 } }
// 函數(shù)被忽略
數(shù)組的concat和slice方法看起來(lái)像深克隆,但他們其實(shí)是淺克隆。
他們會(huì)逐個(gè)把數(shù)組中的值拷貝到另一個(gè)數(shù)組中,類似于這樣:
var arr1 = [1, 2, 3, {a: 4}];
var arr2 = [];
for (var i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
因此對(duì)原數(shù)組進(jìn)行修改不會(huì)影響到克隆的數(shù)組,但是對(duì)原數(shù)組中引用類型元素的修改,會(huì)影響到克隆的數(shù)組。
也就是說(shuō),雖然兩個(gè)數(shù)組指向的是不同的地址,但是數(shù)組中的引用類型元素卻指向了相同的地址。
var array = [1,2,3, {a: 4}];
var array_shallow = array;
var array_concat = array.concat();
var array_slice = array.slice(0);
console.log(array === array_shallow); //true
console.log(array === array_slice); //false,“看起來(lái)”像深拷貝
console.log(array === array_concat); //false,“看起來(lái)”像深拷貝
array.push('hahaha'); // 只有array_shallow被波及
console.log(array_shallow, array_concat, array_slice) // [ 1, 2, 3, { a: 4 }, 'hahaha' ] [ 1, 2, 3, { a: 4 } ] [ 1, 2, 3, { a: 4 } ]
array[3].a = 5; // 全都被波及
console.log(array_shallow, array_concat, array_slice) // [ 1, 2, 3, { a: 5 }, 'hahaha' ] [ 1, 2, 3, { a: 5 } ] [ 1, 2, 3, { a: 5 } ]
如何實(shí)現(xiàn)深克隆呢?當(dāng)然是遞歸復(fù)制了。
對(duì)于對(duì)象或者數(shù)組中的每一個(gè)元素,如果元素為基本類型,那么可以直接賦值target[name] = obj[name],如果元素是對(duì)象或者數(shù)組,則遞歸復(fù)制:target[name] = deepClone(obj[name])。
還有一個(gè)需要考慮的是函數(shù),我們知道函數(shù)也是對(duì)象,所以直接賦值也是淺克?。?/p>
var fn = function () {
console.log(1)
}
fn.a = 1
var fn1 = fn
fn1.a = 2
console.log(fn.a) // 2
雖說(shuō)我們一般不會(huì)給函數(shù)添加屬性,但是為了徹底貫徹“深克隆”的精神,我們可以構(gòu)造一個(gè)新函數(shù)來(lái)實(shí)現(xiàn)復(fù)制:
var fn1 = new Function ('return ' + fn.toString())()
fn1.a = 2
console.log(fn.a) // 1
于是現(xiàn)在可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的深克隆函數(shù)了:
function deepClone(obj) { // 深克隆
if (typeof obj === 'function') { // 函數(shù)
return new Function('return ' + obj.toString())()
}
if (typeof obj !== 'object') { // 基本類型
return obj
}
// 對(duì)象,數(shù)組
var value, target = {}
if (Object.prototype.toString.call(obj) === '[object Array]') { // 數(shù)組
target = []
}
for (var name in obj) {
value = obj[name]
if (value === obj) { // 避免死循環(huán)
continue;
}
if (typeof obj[name] === 'function' || typeof obj[name] === 'object') { // 函數(shù)或者對(duì)象/數(shù)組則遞歸復(fù)制
target[name] = deepClone(obj[name])
} else {
target[name] = obj[name]
}
}
return target
}
var obj1 = deepClone(obj); // 對(duì)象克隆test
console.log(obj.c === obj1.c) // false
obj.a.push(4);
console.log(obj, obj1) // { a: [ 1, 2, 3, 4 ], b: { b1: 1, b2: 2 }, c: [Function: c] } { a: [ 1, 2, 3 ], b: { b1: 1, b2: 2 }, c: [Function] }
var arr = [1, 2, 3, {a: 4}] // 數(shù)組克隆test
var arr1 = deepClone(arr);
console.log(arr === arr1) // false
arr[3].a = 5
console.log(arr1[3].a) // 4
var fn = function () {
console.log('a')
} // 函數(shù)克隆test
var fn1 = deepClone(fn)
console.log(fn, fn1) // [Function: fn] [Function]
fn(); // a
fn1(); // a
console.log(fn === fn1) // false
有了上面的基礎(chǔ),看懂.extend()的源碼就不難了:
//給jQuery對(duì)象和jQuery原型對(duì)象都添加了extend擴(kuò)展方法
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
i = 1, // 下一個(gè)要處理的參數(shù)是argument[i]
length = arguments.length,
deep = false; // 是否為深克隆
//以上其中的變量:options是一個(gè)緩存變量,用來(lái)緩存arguments[i],name是用來(lái)接收將要被擴(kuò)展對(duì)象的key,src改變之前target對(duì)象上每個(gè)key對(duì)應(yīng)的value。
//copy傳入對(duì)象上每個(gè)key對(duì)應(yīng)的value,copyIsArray判定copy是否為一個(gè)數(shù)組,clone深拷貝中用來(lái)臨時(shí)存對(duì)象或數(shù)組的src。
// 處理深拷貝的情況
if (typeof target === "boolean") {
deep = target;
target = arguments[1] || {};
//跳過(guò)布爾值和目標(biāo)
i++;
}
// 控制當(dāng)target不是object或者function時(shí),變成空對(duì)象
if (typeof target !== "object" && !jQuery.isFunction(target)) {
target = {};
}
// 當(dāng)參數(shù)列表長(zhǎng)度等于i的時(shí)候,也就是沒(méi)有要被包含的對(duì)象了,那么擴(kuò)展jQuery對(duì)象自身。
if (length === i) {
target = this; --i;
}
for (; i < length; i++) {
if ((options = arguments[i]) != null) {
// 擴(kuò)展基礎(chǔ)對(duì)象
for (name in options) {
src = target[name]; // 擴(kuò)展的對(duì)象上的該屬性
copy = options[name]; // 當(dāng)前對(duì)象上的該屬性
// 防止死循環(huán),這里舉個(gè)例子,如var i = {};i.a = i;$.extend(true,{},i);如果沒(méi)有這個(gè)判斷變成死循環(huán)了
if (target === copy) {
continue;
}
// 元素為普通對(duì)象或者數(shù)組
if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
if (copyIsArray) { // 元素為數(shù)組
copyIsArray = false;
// 這里可以看出對(duì)于 對(duì)象/數(shù)組 里面的 (對(duì)象/數(shù)組)元素,jq也是擴(kuò)展而不是替換
clone = src && jQuery.isArray(src) ? src: []; // 如果src存在且是數(shù)組的話就讓clone副本等于src否則等于空數(shù)組。
} else {
clone = src && jQuery.isPlainObject(src) ? src: {}; // 如果src存在且是對(duì)象的話就讓clone副本等于src否則等于空對(duì)象。
}
// 遞歸拷貝
target[name] = jQuery.extend(deep, clone, copy);
} else if (copy !== undefined) { // 其他情況直接賦值
target[name] = copy; // 若原對(duì)象存在name屬性,則直接覆蓋掉;若不存在,則創(chuàng)建新的屬性。
}
}
}
}
// 返回修改的對(duì)象
return target;
};
從源碼可以看出在jq中,函數(shù)被處理為淺克隆了。