淺析JavaScript解析賦值、淺拷貝和深拷貝的區別

文章首發於sau交流學習社區數組

1、賦值(Copy)app

賦值是將某一數值或對象賦給某個變量的過程,分爲:函數

一、基本數據類型:賦值,賦值以後兩個變量互不影響post

二、引用數據類型:賦**址**,兩個變量具備相同的引用,指向同一個對象,相互之間有影響學習

對基本類型進行賦值操做,兩個變量互不影響。spa

// saucxs
let a = "saucxs";
let b = a;
console.log(b);  // saucxs

a = "change";
console.log(a);   // change

console.log(b);    // saucxs

對引用類型進行賦**址**操做,兩個變量指向同一個對象,改變變量 a 以後會影響變量 b,哪怕改變的只是對象 a 中的基本類型數據。prototype

// saucxs
let a = {
    name: "saucxs",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = a;
console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "45"}
// } 

a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

console.log(b);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// }

一般在開發中並不但願改變變量 a 以後會影響到變量 b,這時就須要用到淺拷貝和深拷貝。code

 

2、淺拷貝(Shallow Copy)對象

一、什麼是淺拷貝blog

建立一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。若是屬性是基本類型,拷貝的就是基本類型的值,若是屬性是引用類型,拷貝的就是內存地址 ,因此若是其中一個對象改變了這個地址,就會影響到另外一個對象。

 

 

上圖中,`SourceObject` 是原對象,其中包含基本類型屬性 `field1` 和引用類型屬性 `refObj`。淺拷貝以後基本類型數據 `field2` 和 `filed1` 是不一樣屬性,互不影響。但引用類型 `refObj` 仍然是同一個,改變以後會對另外一個對象產生影響。

簡單來講能夠理解爲淺拷貝只解決了第一層的問題,拷貝第一層的**基本類型值**,以及第一層的**引用類型地址**。

 

二、淺拷貝使用場景

2.1 Object.assign()

`Object.assign()` 方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。

有些文章說`Object.assign()` 是深拷貝,其實這是不正確的。

// saucxs
let a = {
    name: "saucxs",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = Object.assign({}, a);
console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "45"}
// } 

a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "55"}
// }

上面代碼改變對象 a 以後,對象 b 的基本屬性保持不變。可是當改變對象 a 中的對象 `book` 時,對象 b 相應的位置也發生了變化。

 

2.2 展開語法 `Spread`

// saucxs
let a = {
    name: "saucxs",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = {...a};
console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "45"}
// } 

a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "55"}
// }

 

2.3 Array.prototype.slice方法

slice不會改變原數組,`slice()` 方法返回一個新的數組對象,這一對象是一個由 `begin`和 `end`(不包括`end`)決定的原數組的**淺拷貝**。

// saucxs
let a = [0, "1", [2, 3]];
let b = a.slice(1);
console.log(b);
// ["1", [2, 3]]

a[1] = "99";
a[2][0] = 4;
console.log(a);
// [0, "99", [4, 3]]

console.log(b);
//  ["1", [4, 3]]

能夠看出,改變 `a[1]` 以後 `b[0]` 的值並無發生變化,但改變 `a[2][0]` 以後,相應的 `b[1][0]` 的值也發生變化。

說明 `slice()` 方法是淺拷貝,相應的還有`concat`等,在工做中面對複雜數組結構要額外注意。

 

3、深拷貝(Deep Copy)

3.1 什麼是深拷貝?

深拷貝會拷貝全部的屬性,並拷貝屬性指向的動態分配的內存。當對象和它所引用的對象一塊兒拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢而且花銷較大。拷貝先後兩個對象互不影響。

 

3.2 使用深拷貝的場景

3.2.1 JSON.parse(JSON.stringify(object))

// saucxs
let a = {
    name: "saucxs",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = JSON.parse(JSON.stringify(a));
console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "45"}
// } 

a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "45"}
// }

徹底改變變量 a 以後對 b 沒有任何影響,這就是深拷貝的魔力。

咱們看下對數組深拷貝效果如何。

// saucxs
let a = [0, "1", [2, 3]];
let b = JSON.parse(JSON.stringify( a.slice(1) ));
console.log(b);
// ["1", [2, 3]]

a[1] = "99";
a[2][0] = 4;
console.log(a);
// [0, "99", [4, 3]]

console.log(b);
//  ["1", [2, 3]]

對數組深拷貝以後,改變原數組不會影響到拷貝以後的數組。

可是該方法有如下幾個問題:

(1)會忽略 `undefined`

(2)會忽略 `symbol`

(3)不能序列化函數

(4)不能解決循環引用的對象

(5)不能正確處理`new Date()`

(6)不能處理正則

 

其中(1)(2)(3) `undefined`、`symbol` 和函數這三種狀況,會直接忽略。

// saucxs
let obj = {
    name: 'saucxs',
    a: undefined,
    b: Symbol('saucxs'),
    c: function() {}
}
console.log(obj);
// {
// 	name: "saucxs", 
// 	a: undefined, 
//  b: Symbol(saucxs), 
//  c: ƒ ()
// }

let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "saucxs"}

 

其中(4)循環引用會報錯

// saucxs
let obj = {
    a: 1,
    b: {
        c: 2,
   		d: 3
    }
}
obj.a = obj.b;
obj.b.c = obj.a;

let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON

 

其中(5)* `new Date` 狀況下,轉換結果不正確。

// saucxs
new Date();
// Mon Dec 24 2018 10:59:14 GMT+0800 (China Standard Time)

JSON.stringify(new Date());
// ""2018-12-24T02:59:25.776Z""

JSON.parse(JSON.stringify(new Date()));
// "2018-12-24T02:59:41.523Z"

解決方法轉成字符串或者時間戳就行了。

// saucxs
let date = (new Date()).valueOf();
// 1545620645915

JSON.stringify(date);
// "1545620673267"

JSON.parse(JSON.stringify(date));
// 1545620658688

 

其中(6)正則狀況下

// saucxs
let obj = {
    name: "saucxs",
    a: /'123'/
}
console.log(obj);
// {name: "saucxs", a: /'123'/}

let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "saucxs", a: {}}

 

PS:爲何會存在這些問題能夠學習一下 JSON。

除了上面介紹的深拷貝方法,

經常使用的還有`jQuery.extend()` 和 `lodash.cloneDeep()`,後面文章會詳細介紹源碼實現。

 

4、總結

   和原數據是否指向同一對象  第一層數據爲基本數據類型  原數據中包含子對象
 賦值 是   改變會使原數據一塊兒改變  改變會使原數據一塊兒改變
 淺拷貝 否   改變不會使原數據一塊兒改變  改變會使原數據一塊兒改變
 深拷貝 否   改變不會使原數據一塊兒改變  改變不會使原數據一塊兒改變

 

5、參考

一、深拷貝和淺拷貝

二、MDN之Object.assign()

三、MDN之展開語法

四、MDN之Array.stypetype.slice()

相關文章
相關標籤/搜索