在學習深拷貝之前,我們要先搞明白什么是深拷貝?
在JS中,數據類型分為基本數據類型和引用數據類型兩種,對于基本數據類型來說,它的值直接存儲在棧內存中,而對于引用類型來說,它在棧內存中僅僅存儲了一個引用,而真正的數據存儲在堆內存中
當我們對數據進行操作的時候,會發生兩種情況
一、基本數據類型
var a = 3;
var b = a;
b = 5;
console.log(a); // 3
console.log(b); // 5
可以看到的是對于基本類型來說,我們將一個基本類型的值賦予 a 變量,接著將 a 的值賦予變量 b ;然后我們修改 b ;可以看到 b 被修改了,而 a 的值沒有被修改,兩個變量都使用的是獨立的數據;
二、引用數據類型
var obj1 = {
a: 1,
b: 2,
c: 3
}
var obj2 = obj1;
obj2.a = 5;
console.log(obj1.a); // 5
console.log(obj2.a); // 5
可以看到的是,兩個對象的值全部被修改了
對象是引用類型的值,對于引用類型來說,我們將 obj1 賦予 obj2 的時候,我們其實僅僅只是將 obj1 存儲在棧堆中的的引用賦予了 obj2 ,而兩個對象此時指向的是在堆內存中的同一個數據,所以當我們修改任意一個值的時候,修改的都是堆內存中的數據,而不是引用,所以只要修改了,同樣引用的對象的值也自然而然的發生了改變
其實,上面的例子就是一個簡單的淺拷貝,而淺拷貝對應的,就是我們文章的主題,深拷貝!
一、 淺拷貝
對于淺拷貝而言,就是只拷貝對象的引用,而不深層次的拷貝對象的值,多個對象指向堆內存中的同一對象,任何一個修改都會使得所有對象的值修改,因為它們公用一條數據
二、深拷貝
我們在實際的項目中,肯定不能讓每個對象的值都指向同一個堆內存,這樣的話不便于我們做操作,所以自然而然的誕生了深拷貝
深拷貝作用在引用類型上!例如:Object,Array
深拷貝不會拷貝引用類型的引用,而是將引用類型的值全部拷貝一份,形成一個新的引用類型,這樣就不會發生引用錯亂的問題,使得我們可以多次使用同樣的數據,而不用擔心數據之間會起沖突
三、深拷貝的實現
- 首先看一下乞丐版的深拷貝吧!JSON.stringify()以及JSON.parse()
var obj1 = {
a: 1,
b: 2,
c: 3
}
var objString = JSON.stringify(obj1);
var obj2 = JSON.parse(objString);
obj2.a = 5;
console.log(obj1.a); // 1
console.log(obj2.a); // 5
可以看到沒有發生引用問題,修改obj2的數據,并不會對obj1造成任何影響
但是為什么說它是乞丐版的呢?
那是因為 使用JSON.stringify()以及JSON.parse()它是不可以拷貝 undefined , function, RegExp 等等類型的
- 接著來看第二種方式 Object.assign(target, source)
var obj1 = {
a: 1,
b: 2,
c: 3
}
var obj2 = Object.assign({}, obj1);
obj2.b = 5;
console.log(obj1.b); // 2
console.log(obj2.b); // 5
第二種方式實現的看起來也沒有任何的問題,但是這是一層對象,如果是有多層嵌套呢
var obj1 = {
a: 1,
b: 2,
c: ['a','b','c']
}
var obj2 = Object.assign({}, obj1);
obj2.c[1] = 5;
console.log(obj1.c); // ["a", 5, "c"]
console.log(obj2.c); // ["a", 5, "c"]
可以看到對于一層對象來說是沒有任何問題的,但是如果對象的屬性對應的是其它的引用類型的話,還是只拷貝了引用,修改的話還是會有問題
- 第三種方式 遞歸拷貝
// 定義一個深拷貝函數 接收目標target參數
function deepClone(target) {
// 定義一個變量
let result;
// 如果當前需要深拷貝的是一個對象的話
if (typeof target === 'object') {
// 如果是一個數組的話
if (Array.isArray(target)) {
result = []; // 將result賦值為一個數組,并且執行遍歷
for (let i in target) {
// 遞歸克隆數組中的每一項
result.push(deepClone(target[i]))
}
// 判斷如果當前的值是null的話;直接賦值為null
} else if(target===null) {
result = null;
// 判斷如果當前的值是一個RegExp對象的話,直接賦值
} else if(target.constructor===RegExp){
result = target;
}else {
// 否則是普通對象,直接for in循環,遞歸賦值對象的所有值
result = {};
for (let i in target) {
result[i] = deepClone(target[i]);
}
}
// 如果不是對象的話,就是基本數據類型,那么直接賦值
} else {
result = target;
}
// 返回最終結果
return result;
}
可以看一下效果
let obj1 = {
a: {
c: /a/,
d: undefined,
b: null
},
b: function () {
console.log(this.a)
},
c: [
{
a: 'c',
b: /b/,
c: undefined
},
'a',
3
]
}
let obj2 = deepClone(obj1);
console.log(obj2);
可以看到最終拷貝的結果是null、undefinde、function、RegExp等特殊的值也全部拷貝成功了,而且我們修改里邊的值也不會有任何問題的
到這里我們就實現了一個簡單的深拷貝,當然,我的這個也只是簡單實現一下,還有很多問題沒有解決,只是給您提供一個思路