浅析JavaScript解析赋值、浅拷贝和深拷贝的区别
文章首發(fā)于sau交流學(xué)習社區(qū)
一、賦值(Copy)
賦值是將某一數(shù)值或?qū)ο?/strong>賦給某個變量的過程,分為:
1、基本數(shù)據(jù)類型:賦值,賦值之后兩個變量互不影響
2、引用數(shù)據(jù)類型:賦**址**,兩個變量具有相同的引用,指向同一個對象,相互之間有影響
對基本類型進行賦值操作,兩個變量互不影響。
// saucxs let a = "saucxs"; let b = a; console.log(b); // saucxsa = "change"; console.log(a); // changeconsole.log(b); // saucxs對引用類型進行賦**址**操作,兩個變量指向同一個對象,改變變量?a?之后會影響變量?b,哪怕改變的只是對象?a?中的基本類型數(shù)據(jù)。
// 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"} // }通常在開發(fā)中并不希望改變變量?a?之后會影響到變量?b,這時就需要用到淺拷貝和深拷貝。
?
二、淺拷貝(Shallow?Copy)
1、什么是淺拷貝
創(chuàng)建一個新對象,這個對象有著原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內(nèi)存地址?,所以如果其中一個對象改變了這個地址,就會影響到另一個對象。
?
?
上圖中,`SourceObject`?是原對象,其中包含基本類型屬性?`field1`?和引用類型屬性?`refObj`。淺拷貝之后基本類型數(shù)據(jù)?`field2`?和?`filed1`?是不同屬性,互不影響。但引用類型?`refObj`?仍然是同一個,改變之后會對另一個對象產(chǎn)生影響。
簡單來說可以理解為淺拷貝只解決了第一層的問題,拷貝第一層的**基本類型值**,以及第一層的**引用類型地址**。
?
2、淺拷貝使用場景
2.1?Object.assign()
`Object.assign()`?方法用于將所有可枚舉屬性的值從一個或多個源對象復(fù)制到目標對象。它將返回目標對象。
有些文章說`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?相應(yīng)的位置也發(fā)生了變化。
?
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不會改變原數(shù)組,`slice()`?方法返回一個新的數(shù)組對象,這一對象是一個由?`begin`和?`end`(不包括`end`)決定的原數(shù)組的**淺拷貝**。
// 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]`?的值并沒有發(fā)生變化,但改變?`a[2][0]`?之后,相應(yīng)的?`b[1][0]`?的值也發(fā)生變化。
說明?`slice()`?方法是淺拷貝,相應(yīng)的還有`concat`等,在工作中面對復(fù)雜數(shù)組結(jié)構(gòu)要額外注意。
?
三、深拷貝(Deep Copy)
3.1 什么是深拷貝?
深拷貝會拷貝所有的屬性,并拷貝屬性指向的動態(tài)分配的內(nèi)存。當對象和它所引用的對象一起拷貝時即發(fā)生深拷貝。深拷貝相比于淺拷貝速度較慢并且花銷較大。拷貝前后兩個對象互不影響。
?
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?沒有任何影響,這就是深拷貝的魔力。
我們看下對數(shù)組深拷貝效果如何。
// 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]]對數(shù)組深拷貝之后,改變原數(shù)組不會影響到拷貝之后的數(shù)組。
但是該方法有以下幾個問題:
(1)會忽略?`undefined`
(2)會忽略?`symbol`
(3)不能序列化函數(shù)
(4)不能解決循環(huán)引用的對象
(5)不能正確處理`new?Date()`
(6)不能處理正則
?
其中(1)(2)(3) `undefined`、`symbol`?和函數(shù)這三種情況,會直接忽略。
// 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)循環(huán)引用會報錯
// 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`?情況下,轉(zhuǎn)換結(jié)果不正確。
// 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"解決方法轉(zhuǎn)成字符串或者時間戳就好了。
// saucxs let date = (new Date()).valueOf(); // 1545620645915JSON.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:為什么會存在這些問題可以學(xué)習一下?JSON。
除了上面介紹的深拷貝方法,
常用的還有`jQuery.extend()`?和?`lodash.cloneDeep()`,后面文章會詳細介紹源碼實現(xiàn)。
?
四、總結(jié)
| ? | ?和原數(shù)據(jù)是否指向同一對象 | ?第一層數(shù)據(jù)為基本數(shù)據(jù)類型 | ?原數(shù)據(jù)中包含子對象 |
| ?賦值 | 是? | ?改變會使原數(shù)據(jù)一起改變 | ?改變會使原數(shù)據(jù)一起改變 |
| ?淺拷貝 | 否? | ?改變不會使原數(shù)據(jù)一起改變 | ?改變會使原數(shù)據(jù)一起改變 |
| ?深拷貝 | 否? | ?改變不會使原數(shù)據(jù)一起改變 | ?改變不會使原數(shù)據(jù)一起改變 |
?
五、參考
1、深拷貝和淺拷貝
2、MDN之Object.assign()
3、MDN之展開語法
4、MDN之Array.stypetype.slice()
轉(zhuǎn)載于:https://www.cnblogs.com/chengxs/p/10788442.html
總結(jié)
以上是生活随笔為你收集整理的浅析JavaScript解析赋值、浅拷贝和深拷贝的区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 英雄联盟(LOL)排位赛中如何能成为队长
- 下一篇: 以JSONobject形式提交http请