開始之前,舉個**栗子**!
```
let a = "笑死你",
let b = a
b = "不想讓你死"
console.log(a) //快說他輸出多少??
console.log(b) //快說他輸出多少??
```
好的 說出答案了,我們不急,知道大家都是前端大佬,咱們慢慢一步一步來哈。
>**函數也是對象** 底層是對象,只不過叫數組類型
```
let c = ["貓", "狗", "驢"]
let d = c
d[0] = “ 羊”
console.log(d)
console.log(c)
```
```
let a = ["貓"]
let b = [...a]
b[0] = "喵喵喵"
console.log(b)
console.log(a)
//這就是深拷貝
```
```
let a = [ {a: "小明"} ]
let b = [...a]
console.log(b)
b[0].a = "汪汪叫"
console.log(b)
console.log(a)
```
可以發現 如果用...方法拷貝過來的是字符串,是可以實現深拷貝的 。但是當拷貝的是對象的話,就不行了
此時此刻,來了,什么是深拷貝,什么是淺拷貝?
## 如何區分深拷貝與淺拷貝,簡單點來說,就是假設B復制了A,當修改B時,看A是否會發生變化,如果A也跟著變了,說明這是淺拷貝,拿人手短,如果A沒變,那就是深拷貝。
* 從基本類型和引用的數據存儲上面區別理解:
**a.基本類型--名值存儲在棧內存中**,例如let a=1;

當你b=a復制時,棧內存會新開辟一個內存,例如這樣:

所以當你此時修改a=2,對b并不會造成影響,因為此時的b已自食其力,翅膀硬了,不受a的影響了。當然,let a=1,b=a;雖然b不受a影響,但這也算不上深拷貝,因為深拷貝本身只針對較為復雜的object類型數據。
**b.引用數據類型--名存在棧內存中,值存在于堆內存中,但是棧內存會提供一個引用的地址指向堆內存中的值**,我們以上面淺拷貝的例子畫個圖:

當b=a進行拷貝時,其實復制的是a的引用地址,而并非堆里面的值。

而當我們**a\[0\]=1**時進行數組修改時,由于a與b指向的是同一個地址,所以自然b也受了影響,這就是所謂的淺拷貝了。
那,要是在堆內存中也開辟一個新的內存專門為b存放值,就像基本類型那樣,豈不就達到深拷貝的效果了嘛

## **現在我們就來實現淺拷貝與深拷貝**
* 實現淺拷貝的方法
· 1. for···in只循環第一層
~~~
// 只復制第一層的淺拷貝
function simpleCopy(obj1) {
let obj2 = Array.isArray(obj1) ? [] : {};
for (let i in obj1) {
obj2[i] = obj1[i];
}
return obj2;
}
let obj1 = {
a: 1,
b: 2,
c: {
d: 3
}
}
let obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // ?
alert(obj2.a); // ?
alert(obj1.c.d); // ?
alert(obj2.c.d); // ?
~~~
提問,以上各輸出啥?
。
。
。
。
。
。
。
。
。
1 3 4 4
· 2. Object.assign方法
Object.assign方法用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象。
~~~jsx
var obj = {
a: 1,
b: 2
}
var obj1 = Object.assign(obj);
obj1.a = 3;
console.log(obj.a) // 3
~~~
· 3. 直接用=賦值
~~~jsx
let a=[0,1,2,3,4],
b=a;
console.log(a===b);
a[0]=1;
console.log(a,b);
~~~
* 實現深拷貝的方法
· 1. 采用遞歸去拷貝所有層級屬性
~~~jsx
function deepClone(obj){
let objClone = Array.isArray(obj)?[]:{};
if(obj && typeof obj==="object"){
for(key in obj){
if(obj.hasOwnProperty(key)){
//判斷ojb子元素是否為對象,如果是,遞歸復制
if(obj[key]&&typeof obj[key] ==="object"){
objClone[key] = deepClone(obj[key]);
}else{
//如果不是,簡單復制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let a=[1,2,3,4],
b=deepClone(a);
a[0]=2;
console.log(a,b);
~~~

· 2. 通過JSON對象來實現深拷貝
>JSON.stringify()的作用是將?JavaScript 對象轉換為 JSON 字符串,JSON.parse()可以將JSON字符串轉為一個對象
~~~jsx
function deepClone2(obj) {
var _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone;
}
~~~
JSON對象實現深拷貝的一些問題
\* 無法實現對對象中方法的深拷貝,會顯示為 undefined
· 3. 通過jQuery的extend方法實現深拷貝
***$*.extend( \[deep \], target, object1 \[, objectN \] )**
**deep**表示是否深拷貝,為true為深拷貝,為false,則為淺拷貝
**target****?Object**類型 目標對象,其他對象的成員屬性將被附加到該對象上。
**object1??objectN**可選。 Object類型 第一個以及第N個被合并的對象。
~~~js
var array = [1,2,3,4];
var newArray = $.extend(true,[],array); // true為深拷貝,false為淺拷貝
~~~
· 4. 手動實現深拷貝
~~~csharp
let obj1 = {
a: 1,
b: 2
}
let obj2 = {
a: obj1.a,
b: obj1.b
}
obj2.a = 3;
alert(obj1.a);
alert(obj2.a);
~~~
· 5. 如果對象的value是基本類型的話,也可以用Object.assign來實現深拷貝,但是要把它賦值給一個空對象
~~~jsx
var obj = {
a: 1,
b: 2
}
var obj1 = Object.assign({}, obj);
obj1.a = 3;
console.log(obj.a);// 1
~~~
· 6. 用slice實現對數組的深拷貝
**slice()** 方法可從已有的數組中返回選定的元素
[slice]([https://www.w3school.com.cn/js/jsref\_slice\_array.asp](https://www.w3school.com.cn/js/jsref_slice_array.asp))
~~~jsx
// 當數組里面的值是基本數據類型,比如String,Number,Boolean時,屬于深拷貝
// 當數組里面的值是引用數據類型,比如Object,Array時,屬于淺拷貝
var arr1 = ["1","2","3"];
var arr2 = arr1.slice(0);
arr2[1] = "9";
console.log("數組的原始值:" + arr1 );
console.log("數組的新值:" + arr2 );
~~~
· 7. 用concat實現對數組的深拷貝
concat() 方法用于連接兩個或多個數組。
該方法不會改變現有的數組,而僅僅會返回被連接數組的一個副本。
[concat]([https://www.w3school.com.cn/jsref/jsref\_concat\_array.asp](https://www.w3school.com.cn/jsref/jsref_concat_array.asp))
~~~jsx
// 當數組里面的值是基本數據類型,比如String,Number,Boolean時,屬于深拷貝
var arr1 = ["1","2","3"];
var arr2 = arr1.concat();
arr2[1] = "9";
console.log("數組的原始值:" + arr1 );
console.log("數組的新值:" + arr2 );
// 當數組里面的值是引用數據類型,比如Object,Array時,屬于淺拷貝
var arr1 = [{a:1},{b:2},{c:3}];
var arr2 = arr1.concat();
arr2[0].a = "9";
console.log("數組的原始值:" + arr1[0].a ); // 數組的原始值:9
console.log("數組的新值:" + arr2[0].a ); // 數組的新值:9
~~~
· 8.直接使用var newObj = Object.create(oldObj),可以達到深拷貝的效果。
~~~jsx
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用對象導致死循環,如initalObj.a = initalObj的情況
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}
~~~
· 9.使用擴展運算符實現深拷貝
~~~jsx
// 當value是基本數據類型,比如String,Number,Boolean時,是可以使用拓展運算符進行深拷貝的
// 當value是引用類型的值,比如Object,Array,引用類型進行深拷貝也只是拷貝了引用地址,所以屬于淺拷貝
var car = {brand: "BMW", price: "380000", length: "5米"}
var car1 = { ...car, price: "500000" }
console.log(car1); // { brand: "BMW", price: "500000", length: "5米" }
console.log(car); // { brand: "BMW", price: "380000", length: "5米" }
~~~
說了這么多,了解深拷貝也不僅僅是為了應付面試題,在實際開發中也是非常有用的。例如后臺返回了一堆數據,你需要對這堆數據做操作,但多人開發情況下,你是沒辦法明確這堆數據是否有其它功能也需要使用,直接修改可能會造成隱性問題,深拷貝能幫你更安全安心的去操作數據,根據實際情況來使用深拷貝,大概就是這個意思。
# **面試題:**
一 、js基本類型的分類以及包含哪些?
基礎類型:undefined 、 null、number、string、boolean、symbol
引用類型:object對象類型(**Object 、Array 、Function 、Data**)
對于這兩種類型有幾個關鍵知識點:
1 基礎類型是按照值進行訪問的,可以操作保存在變量中的實際的值。對于引用類型,javascript是不允許直接訪問值的,不能直接操作對象的內存空間,在操作對象時候,實際操作是引用,而不是實際的引用。
2 基礎類型存在于棧中,

**引用類型的值是同時保存在棧內存和堆內存中的對象**。

二、js的變量的存儲方式 棧(stack) 和 堆(heap)

關于棧和堆有幾個關鍵點需要了解
1 棧(stack)是后進先出,堆(heap)是先進先出。
2 棧(stack)內存是我們手動分配, 堆(heap)內存是自動分配。
三、如何實現淺拷貝?
四、如何實現深拷貝?
五、 [手寫深度比較isEqual](https://www.cnblogs.com/xintangchn/p/13197207.html)
思路:深度比較兩個對象,就是要深度比較對象的每一個元素。=> 遞歸
* 遞歸退出條件:
1. 被比較的是兩個值類型變量,直接用“===”判斷
2. 被比較的兩個變量之一為null,直接判斷另一個元素是否也為null
* 提前結束遞歸:
1. 兩個變量keys數量不同
2. 傳入的兩個參數是同一個變量
* 遞歸工作
深度比較每一個key
```
function isEqual(obj1, obj2){
//其中一個為值類型或null
if(!isObject(obj1) || !isObject(obj2)){
return obj1 === obj2;
}
//判斷是否兩個參數是同一個變量
if(obj1 === obj2){
return true;
}
//判斷keys數是否相等
const obj1Keys = Object.keys(obj1);
const obj2Keys = Object.keys(obj2);
if(obj1Keys.length !== obj2Keys.length){
return false;
}
//深度比較每一個key
for(let key in obj1){
if(!isEqual(obj1[key], obj2[key])){
return false;
}
}
return true;
}
```