深刻淺出深拷貝與淺拷貝

1、基本類型與引用類型

ECMAScript 中數據類型可分爲:markdown

  • 基本類型:String、Number、Boolean、Symbol、null、undefined
  • 引用類型:Object、Array、Date、RegExp、Function等

不一樣類型的存儲方式:函數

  • 基本類型:基本類型值在內存中佔據固定大小,保存在棧內存中
  • 引用類型:引用類型的值是對象,保存在堆內存中,而棧內存保存的對象的變量標識符和對象存儲在堆內存中的存儲地址

不一樣類型的複製方式:性能

  • 基本類型:從一個變量向另一個新變量複製基本類型的值,會建立這個值的一個副本,並將該副本複製給新變量
let foo = 1;
let bar = foo;
console.log(foo === bar); // -> true

// 修改foo變量的值並不會影響bar變量的值
let foo = 233;
console.log(foo); // -> 233
console.log(bar); // -> 1
複製代碼
  • 引用類型:從一個變量向另外一個新變量複製引用類型的值,其實複製的是指針,最終兩個變量最終都指向同一個對象
let foo = {
  name: 'leeper',
  age: 20
}
let bar = foo;
console.log(foo === bar); // -> true

// 改變foo變量的值會影響bar變量的值
foo.age = 19;
console.log(foo); // -> {name: 'leeper', age: 19}
console.log(bar); // -> {name: 'leeper', age: 19}
複製代碼

2、拷貝

  • 淺拷貝(一層):僅僅是複製了引用,彼此之間的操做會互相影響
  • 深拷貝(多層):在堆中從新分配內存,不一樣的地址,相同的值,互不影響

首先深複製和淺複製只針對像 Object, Array 這樣的複雜對象的。簡單來講,淺複製只複製一層對象的屬性,而深複製則遞歸複製了全部層級。spa

2.1 淺拷貝

2.1.1 Object.assign

// 使用Object.assign解決
// 使用Object.assign(),你就能夠沒有繼承就能得到另外一個對象的全部屬性,快捷好用。 
// Object.assign 方法只複製源對象中可枚舉的屬性和對象自身的屬性。
let obj = { a:1, arr:[2,3]};
let res = Object.assign({}, obj)

console.log(res.arr === obj.arr); // true,指向同一個引用
console.log(res === obj); // false
複製代碼

2.1.2 擴展運算符

// 使用擴展運算符(…)來解決
let obj = { a:1, arr:[2,3]};
let res = {...obj};

console.log(res.arr === obj.arr); // true,指向同一個引用
console.log(res === obj); // false
複製代碼

2.1.3 淺拷貝原生實現

const shallowCopy = (sourceObj) => {
  if (typeof sourceObj !== 'object') return;
  let newObj = sourceObj instanceof Array ? [] : {};
  
  for(let key in sourceObj){ 
    if(sourceObj.hasOwnProperty(key)) {
      //只複製元素自身的屬性,不復制原型鏈上的
      newObj[key] = sourceObj[key];
    }
  }
  return newObj;
}

let obj = { a:1, arr:[2,3]};
let res = shallowCopy(obj);
console.log(res.arr === obj.arr); // true,指向同一個引用
console.log(res.a === obj.a); // false
複製代碼

由於淺複製只會將對象的各個屬性進行依次複製,並不會進行遞歸複製,而 JavaScript 存儲對象都是存地址的,因此淺複製會致使 obj.arr 和 shallowObj.arr 指向同一塊內存地址,大概的示意圖以下。prototype

2.2 深拷貝

2.2.1 JSON 序列化

  • JSON.stringify():把一個 js 對象序列化爲一個 JSON 字符串
  • JSON.parse():把 JSON 字符串反序列化爲一個 js 對象
// 能夠經過 JSON.parse(JSON.stringify(object)) 來解決
let a = {
    age: 1,
    jobs: {
        first: 'FE'
    }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
複製代碼

可是該方法也是有侷限性的:指針

  1. 會忽略 undefined
  2. 不能序列化函數(會忽略函數)
  3. 不能解決循環引用的對象

而且該函數是內置函數中處理深拷貝性能最快的。固然若是你的數據中含有以上三種狀況下,可使用 lodash 的深拷貝函數。code

2.2.2 深拷貝的原生實現

const deepCopy = (sourceObj) => {
  if(typeof sourceObj !== 'object') return;
  let newObj = sourceObj instanceof Array ? [] : {};
  
  for(let key in sourceObj){
    if(sourceObj.hasOwnProperty(key)) {
     //只複製元素自身的屬性,不復制原型鏈上的
      newObj[key] = (typeof sourceObj[key] === 'object' ? deepCopy(sourceObj[key]) : sourceObj[key]);
     }
   }
   return newObj;
}

let obj = { a:1, arr:[2,3]};
let res = deepCopy(obj);
console.log(res.arr === obj.arr); // false,指向不一樣的引用
console.log(res === obj); // false
複製代碼

而深複製則不一樣,它不只將原對象的各個屬性逐個複製出去,並且將原對象各個屬性所包含的對象也依次採用深複製的方法遞歸複製到新對象上。這就不會存在上面 obj 和 shallowObj 的 arr 屬性指向同一個對象的問題。orm

2.3 Array 中的拷貝

2.3.1 Array.prototype.slice()

let a = [1, 2, 3, 4];
let b = a.slice();
console.log(a === b); // -> false(當引用類型時須要知足值相等和引用相等才爲 true)

a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
複製代碼

2.3.2 Array.prototype.concat()

let a = [1, 2, 3, 4];
let b = a.concat();
console.log(a === b); // -> false

a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
複製代碼

2.3.3 綜上

看起來 Array 的 slice(), concat() 彷佛是深拷貝,再接着看就知道它們到底是深拷貝仍是淺拷貝:cdn

let a = [[1, 2], 3, 4];
let b = a.slice();
console.log(a === b); // -> false

a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
複製代碼

一樣,對於concat()也進行驗證:對象

![](https://user-gold-cdn.xitu.io/2019/2/22/169156d156f7c222?w=720&h=270&f=jpeg&s=15463)
let a = [[1, 2], 3, 4];
let b = a.concat();
console.log(a === b); // -> false

a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
複製代碼

綜上, Array 的 slice 和 concat 方法並非真正的深拷貝,對於 Array 的第一層的元素是深拷貝,而 Array 的第二層 slice 和 concat 方法是複製引用。因此,Array 的 slice 和 concat 方法都是淺拷貝。

相關文章
相關標籤/搜索