賦值是將某一數值或對象賦給某個變量的過程,分爲下面 2 部分前端
對基本類型進行賦值操做,兩個變量互不影響。webpack
// 木易楊 let a = "muyiy"; let b = a; console.log(b); // muyiy a = "change"; console.log(a); // change console.log(b); // muyiy
對引用類型進行賦址操做,兩個變量指向同一個對象,改變變量 a 以後會影響變量 b,哪怕改變的只是對象 a 中的基本類型數據。git
// 木易楊 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"} // }
一般在開發中並不但願改變變量 a 以後會影響到變量 b,這時就須要用到淺拷貝和深拷貝。github
建立一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。若是屬性是基本類型,拷貝的就是基本類型的值,若是屬性是引用類型,拷貝的就是內存地址 ,因此若是其中一個對象改變了這個地址,就會影響到另外一個對象。web
上圖中,SourceObject
是原對象,其中包含基本類型屬性 field1
和引用類型屬性 refObj
。淺拷貝以後基本類型數據 field2
和 filed1
是不一樣屬性,互不影響。但引用類型 refObj
仍然是同一個,改變以後會對另外一個對象產生影響。面試
簡單來講能夠理解爲淺拷貝只解決了第一層的問題,拷貝第一層的基本類型值,以及第一層的引用類型地址。算法
Object.assign()
Object.assign()
方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。跨域
有些文章說Object.assign()
是深拷貝,其實這是不正確的。數組
// 木易楊 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 的基本屬性保持不變。可是當改變對象 a 中的對象 book
時,對象 b 相應的位置也發生了變化。瀏覽器
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"} // }
經過代碼能夠看出實際效果和 Object.assign()
是同樣的。
Array.prototype.slice()
slice()
方法返回一個新的數組對象,這一對象是一個由 begin
和 end
(不包括end
)決定的原數組的淺拷貝。原始數組不會被改變。
// 木易楊 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
等,在工做中面對複雜數組結構要額外注意。
深拷貝會拷貝全部的屬性,並拷貝屬性指向的動態分配的內存。當對象和它所引用的對象一塊兒拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢而且花銷較大。拷貝先後兩個對象互不影響。
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 沒有任何影響,這就是深拷貝的魔力。
咱們看下對數組深拷貝效果如何。
// 木易楊 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]]
對數組深拷貝以後,改變原數組不會影響到拷貝以後的數組。
可是該方法有如下幾個問題。
一、會忽略 undefined
二、會忽略 symbol
三、不能序列化函數
四、不能解決循環引用的對象
五、不能正確處理new Date()
六、不能處理正則
undefined
、symbol
和函數這三種狀況,會直接忽略。// 木易楊 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"}
// 木易楊 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
狀況下,轉換結果不正確。// 木易楊 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"
解決方法轉成字符串或者時間戳就行了。
// 木易楊 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:爲何會存在這些問題能夠學習一下 JSON。
除了上面介紹的深拷貝方法,經常使用的還有jQuery.extend()
和 lodash.cloneDeep()
,後面文章會詳細介紹源碼實現,敬請期待!
-- | 和原數據是否指向同一對象 | 第一層數據爲基本數據類型 | 原數據中包含子對象 |
---|---|---|---|
賦值 | 是 | 改變會使原數據一同改變 | 改變會使原數據一同改變 |
淺拷貝 | 否 | 改變不會使原數據一同改變 | 改變會使原數據一同改變 |
深拷貝 | 否 | 改變不會使原數據一同改變 | 改變不會使原數據一同改變 |
js 深拷貝 vs 淺拷貝
進階系列文章彙總以下,內有優質前端資料,以爲不錯點個star。
https://github.com/yygmind/blog
我是木易楊,網易高級前端工程師,跟着我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高級前端的世界,在進階的路上,共勉!