【JavaScript】聊一聊js中的淺複製與深複製與手寫實現

我恨bug發表於2024-07-13

前言

什麼是深複製與淺複製?深複製與淺複製是js中處理物件或資料複製操作的兩種方式。‌在聊深淺複製之前咱得了解一下js中的兩種資料型別:

基本資料型別(6種)
String、Number、Object、Boolean、null、undefined、symbol(ES6+)

引用資料型別
Object(function、Array、正規表示式等皆是物件)

  • 資料的儲存方式是什麼?

基本資料: 基本資料型別是存放在棧中的簡單資料段,它們是直接按值存放的,所以可以直接按值訪問
引用型別: 引用型別是存放在堆記憶體中的物件,儲存的在棧記憶體中的一個指標,儲存的是棧記憶體中物件在堆記憶體中的引用地址。透過這個引用地址可以快速查詢到儲存中堆記憶體中的物件。

1.淺複製

1.1 什麼是淺複製

淺複製,指的是建立新的資料,這個資料有著原始資料屬性值的一份精確複製。如果屬性是基本型別,複製的就是基本型別的值。如果屬性是引用型別,複製的就是記憶體地址即淺複製是複製一層,深層次的引用型別則共享記憶體地址。

  • 下面用一張圖來解釋一下淺複製
    image

1.2 淺複製實現方法

1.2.1 assign

var obj = {
	age: 18,
	person: {
		name1: 'fx',
		name2: 'xka'
	},
	list:['hhh','666'],
	love: function () {
		console.log('嘿嘿')
	}
}
var newObj = Object.assign({}, obj);
//因為是淺複製,所以只複製了基本型別的,引用型別還是共享記憶體地址的,即改變obj的應用型別的內容,newObj裡面的引用型別的值也隨之改變
obj.person.name1='xxx'
obj.list[0]='xxx'
console.log(newObj.person.name1) //xxx

1.2.2 slice

const fxArr = ["One", {
	name: "Two",
	age: 20
}, "Three"]
const fxArrs = fxArr.slice(0,)
fxArr[1].name = "four";
console.log(fxArrs[1].name) //four

1.2.3 concat

const fxArr = ["One", {
	name: "Two",
	age: 20
}, "Three"]
const fxArrs = fxArr.concat()
fxArr[1].name = "four";
console.log(fxArrs[1].name) //four

1.2.4 擴充運算子

const fxArr = ["One", {
	name: "Two",
	age: 20
}, "Three"]
const fxArrs = [...fxArr]
fxArr[1].name = "four";
console.log(fxArrs[1].name) //four

2.深複製

2.1 什麼是深複製

深複製開闢一個新的棧,兩個物件屬完成相同,但是對應兩個不同的地址,修改一個物件的屬性,不會改變另一個物件的屬性

  • 下面用一張圖來解釋一下深複製
    image

2.2 淺複製實現方法

2.2.1 JSON.parse(常用)

var obj = {
	age: 18,
	person: {
		name1: 'fx',
		name2: 'xka'
	},
	list:['hhh','666'],
	love: function () {
		console.log('嘿嘿')
	}
}

const obj2=JSON.parse(JSON.stringify(obj));
obj.person.name1='6666'
console.log(obj2.person.name1) //fx
  • 我常用的基本就是JSON.parse了,然而其他的,之前聽過的lodash的cloneDeep,jq的extend我都沒使用過。
  • 但是適用JSON.parse會有一個缺點,就是處理的資料裡面有undefined、function、symbol會被忽略,但是這也是一個優點,可以利用其特性將undefined等資料排除,拿到乾淨的資料。還有一個缺點就是在處理的資料比較大的話,還有效能問題。

3.手寫實現深淺複製

3.1 淺複製

function clone(object){
	const newObj={}
	for(let proto in object){
		if(object.hasOwnProperty(proto)){
			newObj[proto]= object[proto]
		}
	}
	return newObj
}
var obj = {
	age: 18,
	person: {
		name1: 'fx',
		name2: 'xka'
	},
	list:['hhh','666'],
	love: function () {
		console.log('嘿嘿')
	}
}

const obj1=clone(obj)
console.log(obj)
console.log(obj1)

3.2 深複製

// 手寫深複製
function deepClone(obj, hash = new WeakMap()) {
	// 資料過濾
	if (obj === null) return obj; // 如果是null或者undefined我就不進行複製操作 
	if (obj instanceof Date) return new Date(obj);// 如果傳入的物件是日期物件,使用 new Date() 建立一個新的日期物件並返回
	if (obj instanceof RegExp) return new RegExp(obj);// 如果傳入的物件是正規表示式物件,使用 new RegExp() 建立一個新的正規表示式物件並返回
	// 如果傳入的物件不是普通物件(即不是物件型別)或普通的值 如果是函式的話就不需要深複製
	// 因為複製的只需要考慮是否為物件,所以只需要判斷obj是否為物件型別即可,因為null或者undefined在上面已經先過濾掉了,此時就只剩下基本資料型別和函式了
	if (typeof obj !== "object") return obj;
	// 來到此處的話就只剩下物件了,就要進行深複製
	if (hash.get(obj)) return hash.get(obj);
	// 深複製
	// 建立一個新物件,這個新物件和obj物件型別相同
	// 找到的是所屬類原型上的constructor屬性,而原型上的constructor指向的是當前類本身
	let cloneObj = new obj.constructor();
	hash.set(obj, cloneObj);
	for (let key in obj) {
		if (obj.hasOwnProperty(key)) {
			// 實現一個遞迴複製
			cloneObj[key] = deepClone(obj[key], hash);
		}
	}
	return cloneObj;
}
var obj = {
	age: 18,
	person: {
		name1: 'fx',
		name2: 'xka'
	},
	list:['hhh','666'],
	love: function () {
		console.log('嘿嘿')
	}
}


const obj2 = deepClone(obj) // 深複製
const obj3 = Object.assign({}, obj) // 淺複製
const obj4 = clone(obj) // 淺複製
obj.person.name1 = 'hhh';
//因為是深複製,obj2中的引用型別新開闢了一個記憶體地址,所以obj的person改變obj2不受影響
console.log(obj2.person.name1) //fx
//因為是淺複製,obj3、obj4中的引用型別與obj中的引用型別共享記憶體地址,所以obj的person改變obj3、obj4皆受影響
console.log(obj3.person.name1) //hhh
console.log(obj4.person.name1) //hhh

上述為個人學習整理內容,水平有限,如有錯誤之處,望各位園友不吝賜教!如果覺得不錯,請點選推薦和關注!謝謝~๑•́₃•̀๑ [鮮花][鮮花][鮮花]

相關文章