Javascript 中的克隆(拷貝)問題

TONGZ發表於2018-03-30

克隆(也就是拷貝)是javascript中很重要也很常見的問題。克隆就是將一個物件裡的屬性、方法等複製到另一個物件中,且互不影響(即克隆之後,對一個物件進行改動,不會影響到另一物件)。我們今天就來討論一下原生js中克隆的問題。

action~

Javascript 中的克隆(拷貝)問題

現在有一個物件

var obj = {

	name: '隔壁老王',
	age: 60,
	sex: 'male'
	
}
複製程式碼

我們現在想把obj裡的每一個屬性拷貝到一個空物件var obj1 = {}中,那麼需要寫一個克隆方法,首先遍歷一下obj物件,然後把裡面的每一個屬性都拷貝過去。程式碼如下:

var obj = {

	name: '隔壁老王',
	age: 60,
	sex: 'male'

};

var obj1 = {};

function clone(origin, target) {
	for (var prop in origin) {
		target[prop] = origin[prop];
	}
};

clone(obj, obj1);
複製程式碼

在控制檯中檢視obj1,會發現實現了對obj的拷貝。

Javascript 中的克隆(拷貝)問題
我們再對這個方法進行一些完善:有可能使用者在執行clone方法時,只傳origin一個引數,然後將函式執行結果賦給物件obj1,所以我們需要在方法最下方加一個返回值target,返回克隆結果,相應的也要在函式體裡宣告var target = {};

還有一種情況obj1物件裡事先有其他屬性,那麼函式體裡寫var target = {};顯然是不符合預期的,所以要完善成var target = target || {};確保萬無一失。最終該方法完善為:

function clone(origin, target) {
	var target = target || {};
	for (var prop in origin) {
		target[prop] = origin[prop];
	}
	return target;
}
複製程式碼

下面對obj進行拷貝,且obj1本身有自己的屬性,執行程式碼如下:

var obj = {

	name: '隔壁老王',
	age: 60,
	sex: 'male'

};

var obj1 = {

	father: '老王'

};

function clone(origin, target) {
	var target = target || {};
	for (var prop in origin) {
		target[prop] = origin[prop];
	}
	return target;
}

clone(obj, obj1);
複製程式碼

檢視一下拷貝結果

Javascript 中的克隆(拷貝)問題
完美拷貝了obj的所有屬性,又保留了自身屬性。
Javascript 中的克隆(拷貝)問題
到這就結束了麼?要知道js有六大資料型別(本文不討論ES6新增的Symbol型別):number,string,boolean,undefined,null,object。

其中number,string,boolean,undefined,null歸為原始值一類,而object屬於引用值,具體包括狹義的object,array,function。

下面往obj物件里加點引用值,用我們已經寫出來的克隆方法試試:

var obj = {

	name: '隔壁老王',
	age: 60,
	sex: 'male',
	card: ['信用卡', '借記卡', '理髮卡'],
	wife: {

		name: '小劉'

	},
	divorce: function () { }

};

var obj1 = {

	father: '老王'

};

function clone(origin, target) {
	var target = target || {};
	for (var prop in origin) {
		target[prop] = origin[prop];
	}
	return target;
}

clone(obj, obj1);
複製程式碼

貌似拷貝成功了

Javascript 中的克隆(拷貝)問題
當我們對obj物件進行一波這樣的操作:
Javascript 中的克隆(拷貝)問題
再檢視obj1會發現
Javascript 中的克隆(拷貝)問題
在obj1裡也多了美容卡和一個兒子,函式沒受影響。

我們可以得出這樣的結論:對於陣列和物件,用上面那種克隆方法是不行的。因為對於陣列和物件而言,拷貝的是地址,他們指向的都是同一個空間,通過一個物件在這個空間裡面加了東西,另一個物件必然也會受到影響。

而對於函式而言,通過上面這種普通的賦值拷貝,就可以實現,且互不影響,因為函式的克隆會在記憶體中單獨開闢一塊空間。

我們管上面寫的這種克隆方法叫淺度克隆,它可應用於不包含物件(狹義的)和陣列的物件之間的拷貝。有點繞哈~


下面我們來解決一下狹義的物件和陣列的拷貝問題,即我們需要另一種萬全之策——深度克隆

先來整理下思路:

1.遍歷待拷貝的物件; 2.判斷每個元素是不是原始值,若是,則通過淺度克隆的手段進行拷貝; 3.若是引用值,則需要繼續判斷是物件還是陣列; 4.再分別建立空陣列或物件用來盛放裡面即將拷貝而來的屬性值; 5.陣列和物件裡面的若是原始值,則淺拷貝即可實現,若還有引用值,則還需要重複進行上述一系列的判斷。

上述每一步思路怎麼用程式碼實現呢:

1.使用for in進行遍歷。但需要注意的是,for in方法會把物件原型裡的屬性也一起遍歷了,所以要與hasOwnProperty()方法進行聯用,hasOwnProperty()方法可以判斷某屬性是不是該物件自己的屬性,從而過濾掉原型中的屬性。 2.用typeof()返回值來進行判斷,陣列和物件的typeof返回值是'object'。 3.判斷物件還是陣列有多種方法,舉出常見的三種:分別是constructor、instanceof和toString()方法。這裡最好用toString()方法,因為在有父子域之間拷貝的情況,constructor和instanceof這兩種是不好用的。 4.就是[]和{}唄。 5.重複判斷,自然想到遞迴。

深度克隆方法如下:

function deepClone(origin, target) {
	var target = target || {},
		toStr = Object.prototype.toString,
		arrStr = '[object Array]';
	for (var prop in origin) {
		if (origin.hasOwnProperty(prop)) {
			if (typeof (origin[prop]) == 'object' && origin[prop] !== null) {
				if (toStr.call(origin[prop]) == arrStr) {
					target[prop] = [];
				} else {
					target[prop] = {};
				}
				deepClone(origin[prop], target[prop]);
			} else {
				target[prop] = origin[prop];
			}
		}
	}
	return target;
}
複製程式碼

注意到origin[prop] !== null這句了麼?為啥要加上它,因為typeof(null)也是'object'。

來試試吧,我們把obj變得複雜一點,給隔壁老王的老婆增加倆兒子:王小寶和王二寶。執行過程如下:

var obj = {
			name: '隔壁老王',
			age: 60,
			sex: 'male',
			card: ['信用卡', '借記卡', '理髮卡'],
			wife: {
				name: '小劉',
				son: {
					name1: '王小寶',
					name2: '王二寶'
				}
			},
			divorce: function () { }
		};

		var obj1 = {

			father: "老王"

		};

		function deepClone(origin, target) {
			var target = target || {},
				toStr = Object.prototype.toString,
				arrStr = '[object Array]';
			for (var prop in origin) {
				if (origin.hasOwnProperty(prop)) {
					if (typeof (origin[prop]) == 'object' && origin[prop] !== null) {
						target[prop] = (toStr.call(origin[prop]) == arrStr) ? [] : {};
						deepClone(origin[prop], target[prop]);
					} else {
						target[prop] = origin[prop];
					}
				}
			}
			return target;
		}

		deepClone(obj, obj1);
複製程式碼

看看結果,沒毛病。

Javascript 中的克隆(拷貝)問題
對obj的引用值增加一些屬性試試。
Javascript 中的克隆(拷貝)問題
發現obj1紋絲不動,完美拷貝實現!


綜上,克隆方法白話完了,實戰開發中針對實際需要採取不同的克隆手段。

相關文章