摘要:展開語法木易楊通過代碼可以看出實(shí)際效果和是一樣的。木易楊可以看出,改變之后的值并沒有發(fā)生變化,但改變之后,相應(yīng)的的值也發(fā)生變化。深拷貝使用場景木易楊完全改變變量之后對沒有任何影響,這就是深拷貝的魔力。木易楊情況下,轉(zhuǎn)換結(jié)果不正確。
一、賦值(Copy)
賦值是將某一數(shù)值或?qū)ο筚x給某個變量的過程,分為下面 2 部分
基本數(shù)據(jù)類型:賦值,賦值之后兩個變量互不影響
引用數(shù)據(jù)類型:賦址,兩個變量具有相同的引用,指向同一個對象,相互之間有影響
對基本類型進(jìn)行賦值操作,兩個變量互不影響。
// 木易楊 let a = "muyiy"; let b = a; console.log(b); // muyiy a = "change"; console.log(a); // change console.log(b); // muyiy
對引用類型進(jìn)行賦址操作,兩個變量指向同一個對象,改變變量 a 之后會影響變量 b,哪怕改變的只是對象 a 中的基本類型數(shù)據(jù)。
// 木易楊 let a = { name: "muyiy", book: { title: "You Don"t Know JS", price: "45" } } let b = a; console.log(b); // { // name: "muyiy", // 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、淺拷貝使用場景Object.assign()
Object.assign() 方法用于將所有可枚舉屬性的值從一個或多個源對象復(fù)制到目標(biāo)對象。它將返回目標(biāo)對象。
有些文章說Object.assign() 是深拷貝,其實(shí)這是不正確的。
// 木易楊 let a = { name: "muyiy", book: { title: "You Don"t Know JS", price: "45" } } let b = Object.assign({}, a); console.log(b); // { // name: "muyiy", // 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: "muyiy", // book: {title: "You Don"t Know JS", price: "55"} // }
上面代碼改變對象 a 之后,對象 b 的基本屬性保持不變。但是當(dāng)改變對象 a 中的對象 book 時,對象 b 相應(yīng)的位置也發(fā)生了變化。
展開語法 Spread
// 木易楊 let a = { name: "muyiy", book: { title: "You Don"t Know JS", price: "45" } } let b = {...a}; console.log(b); // { // name: "muyiy", // 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: "muyiy", // book: {title: "You Don"t Know JS", price: "55"} // }
通過代碼可以看出實(shí)際效果和 Object.assign() 是一樣的。
Array.prototype.slice()
slice() 方法返回一個新的數(shù)組對象,這一對象是一個由 begin和 end(不包括end)決定的原數(shù)組的淺拷貝。原始數(shù)組不會被改變。
// 木易楊 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) 1、什么是深拷貝深拷貝會拷貝所有的屬性,并拷貝屬性指向的動態(tài)分配的內(nèi)存。當(dāng)對象和它所引用的對象一起拷貝時即發(fā)生深拷貝。深拷貝相比于淺拷貝速度較慢并且花銷較大。拷貝前后兩個對象互不影響。
2、深拷貝使用場景JSON.parse(JSON.stringify(object))
// 木易楊 let a = { name: "muyiy", book: { title: "You Don"t Know JS", price: "45" } } let b = JSON.parse(JSON.stringify(a)); console.log(b); // { // name: "muyiy", // 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: "muyiy", // book: {title: "You Don"t Know JS", price: "45"} // }
完全改變變量 a 之后對 b 沒有任何影響,這就是深拷貝的魔力。
我們看下對數(shù)組深拷貝效果如何。
// 木易楊 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、不能處理正則
undefined、symbol 和函數(shù)這三種情況,會直接忽略。
// 木易楊 let obj = { name: "muyiy", a: undefined, b: Symbol("muyiy"), c: function() {} } console.log(obj); // { // name: "muyiy", // a: undefined, // b: Symbol(muyiy), // c: ? () // } let b = JSON.parse(JSON.stringify(obj)); console.log(b); // {name: "muyiy"}
循環(huán)引用情況下,會報(bào)錯。
// 木易楊 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
new Date 情況下,轉(zhuǎn)換結(jié)果不正確。
// 木易楊 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)成字符串或者時間戳就好了。
// 木易楊 let date = (new Date()).valueOf(); // 1545620645915 JSON.stringify(date); // "1545620673267" JSON.parse(JSON.stringify(date)); // 1545620658688
正則情況下,
// 木易楊 let obj = { name: "muyiy", a: /"123"/ } console.log(obj); // {name: "muyiy", a: /"123"/} let b = JSON.parse(JSON.stringify(obj)); console.log(b); // {name: "muyiy", a: {}}
PS:為什么會存在這些問題可以學(xué)習(xí)一下 JSON。
除了上面介紹的深拷貝方法,常用的還有jQuery.extend() 和 lodash.cloneDeep(),后面文章會詳細(xì)介紹源碼實(shí)現(xiàn),敬請期待!
四、總結(jié)-- | 和原數(shù)據(jù)是否指向同一對象 | 第一層數(shù)據(jù)為基本數(shù)據(jù)類型 | 原數(shù)據(jù)中包含子對象 |
---|---|---|---|
賦值 | 是 | 改變會使原數(shù)據(jù)一同改變 | 改變會使原數(shù)據(jù)一同改變 |
淺拷貝 | 否 | 改變不會使原數(shù)據(jù)一同改變 | 改變會使原數(shù)據(jù)一同改變 |
深拷貝 | 否 | 改變不會使原數(shù)據(jù)一同改變 | 改變不會使原數(shù)據(jù)一同改變 |
js 深拷貝 vs 淺拷貝進(jìn)階系列目錄Java 深拷貝和淺拷貝
MDN 之 Object.assign()
MDN 之展開語法
MDN 之 Array.prototype.slice()
【進(jìn)階1期】 調(diào)用堆棧
【進(jìn)階2期】 作用域閉包
【進(jìn)階3期】 this全面解析
【進(jìn)階4期】 深淺拷貝原理
【進(jìn)階5期】 原型Prototype
【進(jìn)階6期】 高階函數(shù)
【進(jìn)階7期】 事件機(jī)制
【進(jìn)階8期】 Event Loop原理
【進(jìn)階9期】 Promise原理
【進(jìn)階10期】Async/Await原理
【進(jìn)階11期】防抖/節(jié)流原理
【進(jìn)階12期】模塊化詳解
【進(jìn)階13期】ES6重難點(diǎn)
【進(jìn)階14期】計(jì)算機(jī)網(wǎng)絡(luò)概述
【進(jìn)階15期】瀏覽器渲染原理
【進(jìn)階16期】webpack配置
【進(jìn)階17期】webpack原理
【進(jìn)階18期】前端監(jiān)控
【進(jìn)階19期】跨域和安全
【進(jìn)階20期】性能優(yōu)化
【進(jìn)階21期】VirtualDom原理
【進(jìn)階22期】Diff算法
【進(jìn)階23期】MVVM雙向綁定
【進(jìn)階24期】Vuex原理
【進(jìn)階25期】Redux原理
【進(jìn)階26期】路由原理
【進(jìn)階27期】VueRouter源碼解析
【進(jìn)階28期】ReactRouter源碼解析
交流進(jìn)階系列文章匯總?cè)缦?,?nèi)有優(yōu)質(zhì)前端資料,覺得不錯點(diǎn)個star。
https://github.com/yygmind/blog
我是木易楊,網(wǎng)易高級前端工程師,跟著我每周重點(diǎn)攻克一個前端面試重難點(diǎn)。接下來讓我?guī)阕哌M(jìn)高級前端的世界,在進(jìn)階的路上,共勉!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/100733.html
摘要:木易楊注意原始類型被包裝為對象木易楊原始類型會被包裝,和會被忽略。木易楊原因在于時,其屬性描述符為不可寫,即。木易楊解決方法也很簡單,使用我們在進(jìn)階期中介紹的就可以了,使用如下。 引言 上篇文章介紹了賦值、淺拷貝和深拷貝,其中介紹了很多賦值和淺拷貝的相關(guān)知識以及兩者區(qū)別,限于篇幅只介紹了一種常用深拷貝方案。 本篇文章會先介紹淺拷貝 Object.assign 的實(shí)現(xiàn)原理,然后帶你手動實(shí)...
摘要:它將返回目標(biāo)對象。有些文章說是深拷貝,其實(shí)這是不正確的。深拷貝相比于淺拷貝速度較慢并且花銷較大。拷貝前后兩個對象互不影響。使用深拷貝的場景完全改變變量之后對沒有任何影響,這就是深拷貝的魔力。 一、賦值(Copy) 賦值是將某一數(shù)值或?qū)ο筚x給某個變量的過程,分為: 1、基本數(shù)據(jù)類型:賦值,賦值之后兩個變量互不影響 2、引用數(shù)據(jù)類型:賦址,兩個變量具有相同的引用,指向同一個對象,相互之間有...
摘要:今天這篇文章我們來看看一道必會面試題,即如何實(shí)現(xiàn)一個深拷貝。木易楊注意這里使用上面測試用例測試一下一個簡單的深拷貝就完成了,但是這個實(shí)現(xiàn)還存在很多問題。 引言 上篇文章詳細(xì)介紹了淺拷貝 Object.assign,并對其進(jìn)行了模擬實(shí)現(xiàn),在實(shí)現(xiàn)的過程中,介紹了很多基礎(chǔ)知識。今天這篇文章我們來看看一道必會面試題,即如何實(shí)現(xiàn)一個深拷貝。本文會詳細(xì)介紹對象、數(shù)組、循環(huán)引用、引用丟失、Symbo...
摘要:而大多數(shù)實(shí)際項(xiàng)目中,我們想要的結(jié)果是兩個變量初始值相同互不影響。所以就要使用到拷貝分為深淺兩種深淺拷貝的區(qū)別淺拷貝只復(fù)制一層對象的屬性,而深拷貝則遞歸復(fù)制了所有層級。 為什么會用到淺拷貝和深拷貝 首先來看一下如下代碼 let a = b = 2 a = 3 console.log(a) console.log(b) let c = d = [1,2,3] let e = f = {a:...
摘要:而大多數(shù)實(shí)際項(xiàng)目中,我們想要的結(jié)果是兩個變量初始值相同互不影響。所以就要使用到拷貝分為深淺兩種深淺拷貝的區(qū)別淺拷貝只復(fù)制一層對象的屬性,而深拷貝則遞歸復(fù)制了所有層級。 為什么會用到淺拷貝和深拷貝 首先來看一下如下代碼 let a = b = 2 a = 3 console.log(a) console.log(b) let c = d = [1,2,3] let e = f = {a:...
閱讀 3751·2021-09-22 15:49
閱讀 3310·2021-09-08 09:35
閱讀 1426·2019-08-30 15:55
閱讀 2326·2019-08-30 15:44
閱讀 719·2019-08-29 16:59
閱讀 1605·2019-08-29 16:16
閱讀 485·2019-08-28 18:06
閱讀 897·2019-08-27 10:55