# 淺拷貝與深拷貝
## 什么時(shí)候需要拷貝
賦值是將某一數(shù)值或?qū)ο筚x給某個(gè)變量的過(guò)程,分為下面 2 部分
- 基本數(shù)據(jù)類型:賦值,賦值之后兩個(gè)變量互不影響
- 引用數(shù)據(jù)類型:賦址,兩個(gè)變量具有相同的引用,指向同一個(gè)對(duì)象,相互之間有影響
對(duì)基本類型進(jìn)行賦值操作,兩個(gè)變量互不影響。
```js
let a = "Libai";
let b = a;
console.log(b); // Libai
a = "xiaobai";
console.log(a); // xiaobai
console.log(b); // Libai
```
對(duì)引用類型進(jìn)行賦址操作,兩個(gè)變量指向同一個(gè)對(duì)象,改變變量 a 之后會(huì)影響變量 b,哪怕改變的只是對(duì)象 a 中的基本類型數(shù)據(jù)。
```js
let a = [1, 2, 3]
let b = a;
b[0] = 100;
console.log(a) // [100, 2, 3]
console.log(b) // [100, 2, 3]
```
通常在開(kāi)發(fā)中并不希望改變變量 b 之后會(huì)影響到變量 a,這時(shí)就需要用到淺拷貝和深拷貝。
## 淺拷貝(Shallow Copy)
```js
let a = [1, 2, 3];
let b = a.slice();
b[0] = 100;
console.log(a) // [1, 2, 3]
console.log(b) // [100, 2, 3]
```
當(dāng)修改`b`的時(shí)候,`a`的值并不改變。什么原因? 因?yàn)檫@里`b`是`a`淺拷貝后的結(jié)果,`b`和`a`現(xiàn)在引用的已經(jīng)不是同一塊空間啦!
淺拷貝有一個(gè)限制性的問(wèn)題, 就是只會(huì)拷貝嵌套對(duì)象的第一層, 只能拷貝一層對(duì)象。
```js
let a = [1, 2, 3, {aa: 22}];
let b = a.slice();
b[3].aa = 100;
b[0] = 10086;
console.log(a) // [1, 2, 3, {aa: 100}]
console.log(b) // [10086, 2, 3, {aa: 100}]
```
如果想拷貝多層的話, 這時(shí)候就需要深拷貝了, 不過(guò)會(huì)在后面講解.
先來(lái)列一下實(shí)現(xiàn)淺拷貝的方法吧~~
## - 手動(dòng)實(shí)現(xiàn)
老規(guī)矩, 循環(huán)解法~
```js
const shallowClone = (target) => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = target[prop];
}
}
return cloneTarget;
} else {
return target;
}
}
```
## - Object.assign()
`Object.assign()`方法可以把任意多個(gè)的源對(duì)象自身的可枚舉屬性拷貝給目標(biāo)對(duì)象,然后返回目標(biāo)對(duì)象。但是`Object.assign()`進(jìn)行的是淺拷貝,拷貝的是對(duì)象的屬性的引用,而不是對(duì)象本身。
```js
const obj1 = {x: 1, y: 2};
const obj2 = Object.assign({}, obj1);
obj2.x = 2;
console.log(obj1) //{x: 1, y: 2} //原對(duì)象未改變
console.log(obj2) //{x: 2, y: 2}
const obj1 = {
x: 1,
y: {
m: 1
}
};
const obj2 = Object.assign({}, obj1);
obj2.y.m = 2;
console.log(obj1) //{x: 1, y: {m: 2}} 原對(duì)象也被改變
console.log(obj2) //{x: 2, y: {m: 2}}
```
## - Array.concat()
針對(duì)數(shù)組能實(shí)現(xiàn)類似效果的還有slice()和Array.from()等, 開(kāi)頭的例子
```js
const arr = [1,2,3,4,[5,6]];
const copy = arr.concat();
copy[0] = 2;
console.log(arr) // [1,2,3,4,[5,6]];
// slice & 擴(kuò)展運(yùn)算符
const copy = arr.slice();
const copy = [...arr];
```
## 深拷貝(Deep Copy)
劃重點(diǎn), 面試必備, 學(xué)不學(xué)(掉不掉頭發(fā))自己看著辦~~
## 1. 簡(jiǎn)單實(shí)現(xiàn)
其實(shí)深拷貝可以拆分成 2 步,淺拷貝 + 遞歸,淺拷貝時(shí)判斷屬性值是否是對(duì)象,如果是對(duì)象就進(jìn)行遞歸操作,兩個(gè)一結(jié)合就實(shí)現(xiàn)了深拷貝。
```js
const isObject = (target) => typeof target === 'object' && target !== null;
// 兼容數(shù)組和對(duì)象
const deepClone = (target) => {
if (isObject(target)) {
const newObj = Array.isArray(target) ? []: {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
if (isObject(target[key])) {
newObj[key] = deepClone(target[key])
} else {
newObj[key] = target[key]
}
}
}
return newObj;
} else {
return target;
}
}
typeof null //"object"
typeof {} //"object"
typeof [] //"object"
typeof function foo(){} //"function" (特殊情況)
```
## 2. 循環(huán)引用
```js
let obj = {val : 100};
obj.target = obj;
deepClone(obj); // Uncaught RangeError: Maximum call stack size exceeded
```
這就是循環(huán)引用。我們?cè)趺磥?lái)解決這個(gè)問(wèn)題呢?
### 1. 使用哈希表
解決方案很簡(jiǎn)單,其實(shí)就是循環(huán)檢測(cè),我們?cè)O(shè)置一個(gè)數(shù)組或者哈希表存儲(chǔ)已拷貝過(guò)的對(duì)象,當(dāng)檢測(cè)到當(dāng)前對(duì)象已存在于哈希表中時(shí),取出該值并返回即可。
```js
const deepClone = (target, hash = new WeakMap()) => {
if(hash.has(target)){
return hash.get(target) // 查哈希表
}
if (isObject(target)) {
const newObj = Array.isArray(target) ? []: {};
hash.set(target, newObj) // 哈希表設(shè)值
for (let key in target) {
if (target.hasOwnProperty(key)) {
if(isObject(target[key])){
newObj[key] = deepClone(target[key], hash); // 傳入哈希表
} else {
newObj[key] = target[key]
}
}
}
return newObj;
} else {
return target;
}
}
```
測(cè)試一下,看看效果如何。
```js
var a = {
name: "Libai",
book: {
title: "You Don't Know JS",
price: "45"
},
a1: undefined,
a2: null,
a3: 123
}
a.target = a;
console.log(deepClone(a))
// {
// name: "Libai",
// a1: undefined,
// a2: null,
// a3: 123,
// book: {title: "You Don't Know JS", price: "45"},
// circleRef: {name: "Libai", book: {…}, a1: undefined, a2: null, a3: 123, …}
// }
```
完美!
### 2. 使用數(shù)組
上面使用了`ES6`中的 `WeakMap` 來(lái)處理,那在 `ES5` 下應(yīng)該如何處理呢?
也很簡(jiǎn)單,使用數(shù)組來(lái)處理就好啦,代碼如下。
```js
const deepClone = (target, uniqueList = []) => {
if (isObject(target)) {
const newObj = Array.isArray(target) ? []: {};
// 數(shù)據(jù)已經(jīng)存在,返回保存的數(shù)據(jù)
const uniqueData = find(uniqueList, target)
if (uniqueData) {
return uniqueData.target
}
// 數(shù)據(jù)不存在, 保存源數(shù)據(jù)
uniqueList.push({
target,
newObj
})
for (let key in target) {
if (target.hasOwnProperty(key)) {
if(isObject(target[key])){
newObj[key] = deepClone(target[key], uniqueList); // 傳入哈希表
} else {
newObj[key] = target[key]
}
}
}
return newObj;
} else {
return target;
}
}
// 用于查找
function find(arr, item){
for(let i = 0; i < arr.length; i++){
if(arr[i].target === item){
return arr[i]
}
}
return null
}
```
浙公網(wǎng)安備 33010602011771號(hào)