首先仍是得回到Javascript的基本數據類型。前端
[深拷貝]
:數值Num、布爾值Boolean、字符串String、null、undefined。基本類型值是指在棧內存保存的簡單數據段,在複製基本類型值的時候,會開闢出一個新的內存空間,將值複製到新的內存空間,舉個栗子:算法
var a = 1; var b = a; a = 2; console.log(a);//輸出2; console.log(b);//輸出1;
[淺拷貝]
:對象、數組、函數等。用類型值是保存在堆內存中的對象,變量保存的只是指向該內存的地址,在複製引用類型值的時候,其實只複製了指向該內存的地址,舉個栗子:數組
var a={b:1} var a2 = a; a2.b = 2; console.log(a) // 輸出 {b: 2}
因此深拷貝問題的出現就是爲了解決引用類型的數據的淺拷貝特性瀏覽器
JSON.parse()
&& JSON.stringfy()
將該對象轉換爲其 JSON 字符串表示形式,而後將其解析回對象。這感受有點太過簡單了,但它確實有效:數據結構
const obj = /* ... */; const copy = JSON.parse(JSON.stringify(obj));
優勢是,若是沒有循環對象,而且不須要保留內置類型,使用該方法皆能夠得到最快的跨瀏覽器的克隆性能。
這裏的缺點是建立了一個臨時的,可能很大的字符串,只是爲了把它從新放回解析器。
另外一個缺點是這種方法不能處理循環對象,並且循環對象常常發生。
例如,當咱們構建樹狀數據結構,其中一個節點引用其父級,而父級又引用其子級。異步
const x = {}; const y = {x}; x.y = y; // Cycle: x.y.x.y.x.y.x.y.x... const copy = JSON.parse(JSON.stringify(x)); // throws!
另外,諸如 Map
, Set
, RegExp
, Date
, ArrayBuffer
和其餘內置類型在進行序列化時會丟失。函數
MessageChannel
&& postMessage
結構化克隆算法
這種方法的缺點是它是異步的。雖然這並沒有大礙,可是有時候你須要使用同步的方式來深度拷貝一個對象。post
function structuralClone(obj) { return new Promise(resolve => { const {port1, port2} = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }); } const obj = /* ... */; const clone = await structuralClone(obj);
Array.slice()
和Array.concat()
方法屬於深拷貝嗎?這個我都被弄糊塗了,網上找了些資料才捋清了一下。性能
對於一維數組而言:指針
arrayObj.slice(start, [end])
var arr1 = ["1","2","3"]; var arr2 = arr1.slice(0); arr2[1] = "9"; console.log("數組的原始值:" + arr1 ); //1,2,3 console.log("數組的新值:" + arr2 ); //1,9,3
arrayObj.concat(arr1,arr2 ... )
var arr1 = ["1","2","3"]; var arr2 = arr1.concat(); arr2[1] = "9"; console.log("數組的原始值:" + arr1 ); //1,2,3 console.log("數組的新值:" + arr2 );//1,9,3
那數組裏面若是包含對象呢?:
var arr1 = [{"name":"weifeng"},{"name":"boy"}];//原數組 var arr2 = [].concat(arr1);//拷貝數組 arr1[1].name="girl"; console.log(arr1);// [{"name":"weifeng"},{"name":"girl"}] console.log(arr2);//[{"name":"weifeng"},{"name":"girl"}] var a1=[["1","2","3"],"2","3"],a2; a2=a1.slice(0); a1[0][0]=0; //改變a1第一個元素中的第一個元素 console.log(a2[0][0]); //影響到了a2
從上面兩個例子能夠看出,因爲數組內部屬性值爲引用對象,所以使用slice和concat對對象數組的拷貝,整個拷貝仍是淺拷貝,拷貝以後數組各個值的指針仍是指向相同的存儲地址。
Array.slice()
和Array.concat()
這兩個方法,僅適用於對不包含引用對象的一維數組的深拷貝!
...
Object.assign()考察點是ES6中實現對象複製,關於Object.assign()這個函數這裏有一篇文章講得很是詳細明白。
ES6提供了Object.assign(),用於合併/複製對象的屬性。
Object.assign(target, source_1, ..., source_n)
下面是一個例子
var o1 = { a: 1, b: 1, c: 1 }; var o2 = { b: 2, c: 2 }; var o3 = { c: 3 }; var obj = Object.assign({}, o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 }
那麼Object.assign()方法是淺拷貝仍是深拷貝呢?請看下面這個例子:
function mutateDeepObject(obj) { obj.a.thing = true; } const obj = {a: {thing: false}}; const copy = Object.assign({}, obj); mutateDeepObject(copy) console.log(obj.a.thing); // prints true
Object.assign(target, sources...)
是一個簡單的拷貝對象的方式,屬於淺拷貝。它接受任意數量的源對象,主要做用就是枚舉它們的全部屬性並分配給target
。
...
使用對象擴展操做符 ...,對象本身的可枚舉屬性能夠被拷貝到新對象。
const obj = { a: 1, b: 2, c:{d:'d'} } const shallowClone = { ...obj } shallowClone.a = 'a'; shallowClone.c.d = '4'; console.log(obj); // a: 1, b: 2, c: {d: "4"}} console.log(shallowClone); // a: "a", b: 2, c: {d: "4"}}
const obj = { a: 1, b: 2, c:{d:'d'} } const shallowClone = Object.keys(obj).reduce((acc, key) => (acc[key] = obj[key], acc), {}); shallowClone.a = 'a'; shallowClone.c.d = '4'; console.log(obj); // a: 1, b: 2, c: {d: "4"}} console.log(shallowClone); // a: "a", b: 2, c: {d: "4"}}