已經有不少關於深拷貝與淺拷貝
的文章,爲何本身還要寫一遍呢💯javascript
❝學習就比如是座大山,人們沿着不一樣的路爬山,分享着本身看到的風景。你不必定能看到別人看到的風景,體會到別人的心情。只有本身去爬山,才能看到不同的風景,體會才更加深入。java
❞
分享一個不錯的思惟導圖👇git
經過文本的總結,但願能夠明白:github
本章節直接從拷貝開始提及,對於基本數據類型,引用數據類型以前的區別,能夠看看上面的思惟導圖👆web
或者看看我以前的章節補一補基礎,有寫的不對的地方歡迎指出!面試
對於引用數據類型的話,細分能夠分爲下面三個方面segmentfault
引用類型的賦值是傳址。只是改變指針的指向,例如,引用類型的賦值是對象保存在棧中的地址的賦值,這樣的話兩個變量就指向同一個對象,所以二者之間操做互相有影響。例如:數組
var a = {}; // a保存了一個空對象的實例
var b = a; // a和b都指向了這個空對象 a.name = 'jozo'; console.log(a.name); // 'jozo' console.log(b.name); // 'jozo' b.age = 22; console.log(b.age);// 22 console.log(a.age);// 22 console.log(a == b);// true 複製代碼
這樣子的狀況,會致使a和b指向同一份數據,對其中一個進行修改數據的話,會影響到另一個,實際開發中,這不是咱們預期中的結果,這會照成某種程度上的bug。編輯器
那麼咱們如何不讓相互之間產生影響呢?一種簡單的辦法就是拷貝一份a變量的數據,因此「根據拷貝的層次不一樣能夠分爲淺拷貝和深拷貝」,淺拷貝的話知識進行一層拷貝,深拷貝的話是無限層次的拷貝!函數
咱們先來實現一個淺拷貝
let shallowClone = source => {
let target = {} for(let i in source) { if( source.hasOwnProperty(i) ) target[i] = source[i]; } return target } let demo = { b:{ c : { } } } let demo2 = shallowClone(demo) let demo3 = demo; console.log(demo3 === demo ) // true console.log(demo2.b.c === demo.b.c ) // true console.log(demo2.b === demo.b ) // true console.log(demo2 === demo ) // false 複製代碼
demo3 = demo 賦值的話,是地址的賦值,也就是說指向同一個對象,那麼不是咱們想要的結果,咱們來看看shallowClone函數,這個是淺拷貝的一種實現方式,那麼demo2變量應該就是實現了一層的拷貝,正如20行效果,demo2變量是在堆中開了一個新內存,因此二者指向不一樣對象,demo2.b === demo.b 爲 true 說明 這就是淺拷貝效果,簡單的拷貝一層,那麼咱們是否是能夠遞歸的思想去完成深拷貝呢?
Object.assign()
方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。
let demo = {
name : 'dayday', book : { title : 'Do you really Know JS', price : "45" } } let clone_demo = Object.assign({}, demo) console.log(clone_demo); demo.name = 'new name' demo.book.price = '100' console.log(clone_demo.name,clone_demo.book.price); // dayday 100 複製代碼
修改上面代碼demo變量以後,對象clone_demo基本屬性沒有改變,可是修改demo對象中book引用屬性時,對象clone_demo相應位置屬性值也發生改變,一樣的接下來展開運算符也是同樣效果👇
let demo = {
name : 'dayday', book : { title : 'Do you really Know JS', price : "45" } } let clone_demo = {...demo} console.log(clone_demo); demo.name = 'new name' demo.book.price = '100' console.log(clone_demo.name,clone_demo.book.price); // dayday 100 複製代碼
咱們能夠看到展開運算… 效果跟Object.assign() 效果是同樣的。
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
等,在工做中面對複雜數組結構要額外注意。
深拷貝會拷貝全部的屬性,並拷貝屬性指向的動態分配的內存。當對象和它所引用的對象一塊兒拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢而且花銷較大。拷貝先後兩個對象互不影響。
let demo = {
name : 'dayday', book : { title : 'Do you really Know JS', price : "45" } } let clone_demo = JSON.parse(JSON.stringify(demo)) console.log(clone_demo); demo.name = 'new name' demo.book.price = '100' console.log(clone_demo.name,clone_demo.book.price); // dayday 45 複製代碼
徹底改變變量 demo 以後對 clone_demo 沒有任何影響,這就是深拷貝的魔力。
一樣的對於數組使用該方法也是能夠達到深拷貝的。
對於undefined
symbol
函數
三種狀況會直接忽略
let demo = {
name : 'dayday', h1 : undefined, h2 : Symbol('dayday'), h3 : function () {}, } let clone_demo = JSON.parse(JSON.stringify(demo)) console.dir(clone_demo) // { name : 'dayday' } 複製代碼
循環引用狀況下,會報錯。
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();
// Wed Jul 01 2020 16:19:07 GMT+0800 (中國標準時間) {} JSON.stringify(new Date()); // ""2020-07-01T08:19:19.860Z"" JSON.parse(JSON.stringify(new Date())); // "2020-07-01T08:19:35.569Z" 複製代碼
解決方法轉成字符串或者時間戳就行了
let date = (new Date()).valueOf();
// 1593591638596 JSON.stringify(date); // "1593591638596" JSON.parse(JSON.stringify(date)); // 1593591638596 複製代碼
正則狀況下
let demo = {
name: "daydaylee", a: /'123'/ } console.log(demo); // {name: "daydaylee", a: /'123'/} let clone_demo = JSON.parse(JSON.stringify(obj)); console.log(clone_demo); // {name: "daydaylee", a: {}} 複製代碼
「PS:爲何會存在這些問題能夠學習一下 JSON」
除了上面介紹的深拷貝方法,經常使用的還有jQuery.extend()
和 lodash.cloneDeep()
,因爲文章篇幅的問題,這裏就很少介紹了,有興趣的能夠本身去了解了解
面試官叫你實現一個深拷貝的話,你只要記得淺拷貝+遞歸,淺拷貝的時候,去判斷是否是一個對象就行的,是對象的話,就進行遞歸操做。
以前的簡單淺拷貝:
let shallowClone = source => {
let target = {} for(let key in source) { if(Object.prototype.hasOwnProperty.call(source, key)){ target[key] = typeof source[key] === 'object' ? shallowClone(source[key]) : source[key]; } } return target } let demo = { name : 'dayday', book : { title : 'Do you really Know JS', price : "45" } } let clone_demo = shallowClone(demo); console.log(clone_demo); demo.name = 'new name' demo.book.price = '100' console.log(clone_demo.name,clone_demo.book.price) // dayday 45 複製代碼
寫到這裏,至少一個簡單的深克隆實現了,可是仍是有些問題沒有解決!
typeof null === object
首先的寫一個兼容數組而且判斷null方法的函數
let isObject = obj => typeof obj === 'object' && obj !== null ;
複製代碼
那麼進一步完善了深度拷貝的方法
// 保留數組 而且判斷是否是null
let isObject = obj => typeof obj === 'object' && obj !== null ; let shallowClone2 = source => { if(!isObject(source)) return source // 非對象返回自身 let target = Array.isArray(source) ? [] : {} for(let key in source) { if(Object.prototype.hasOwnProperty.call(source, key)){ target[key] = isObject(source[key]) ? shallowClone2(source[key]) : source[key]; } } return target } let demo = { name : 'dayday', book : { title : 'Do you really Know JS', price : "45" }, h1 : null, h2 : [1,2,3], h3 : undefined } let clone_demo = shallowClone2(demo); console.log(clone_demo); demo.name = 'new name' demo.book.price = '100' demo.h2[1] = 'new data' console.log(clone_demo.name,clone_demo.book.price) // dayday 45 console.log(clone_demo); // 修改demo值爲能影響clone_demo 複製代碼
這篇文章寫的很好:深拷貝的終極探索(99%的人都不知道)
它還對深度拷貝有了新的優化,好比JSON.parse(JSON.stringify(obj))循環引用拋出異常的問題,作出了優化,那咱們試着去優化這個小問題。
對於循環檢測的話,咱們可使用哈希檢測的方法,好比設置一個數組或者是已經拷貝的對象,當檢測到對象已經存在哈希表時,就取出該值🤭
let isObject = obj => typeof obj === 'object' && obj !== null;
let shallowClone3 = (source, hash = new WeakMap()) => { if (!isObject(source)) return source // 非對象返回自身 if (hash.has(source)) return hash.get(source) // 新增檢測, 查哈希表 let target = Array.isArray(source) ? [] : {} hash.set(source, target) // 設置哈希表值 for (let key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = isObject(source[key]) ? shallowClone3(source[key], hash) : source[key]; // 傳入哈希表 } } return target } let obj = { a: 1, b: { c: 2, d: 3 } } obj.a = obj.b; obj.b.c = obj.a; let clone_obj = shallowClone3(obj) console.log(clone_obj) 複製代碼
寫完這段代碼的話,至少面試實現一個這樣子的深拷貝馬馬虎虎過的去,固然了仍是有不少的問題須要解決的:
固然了有興趣的讀者能夠深刻的瞭解吶🚀
-- | 和原數據是否指向同一對象 | 第一層數據爲基本數據類型 | 原數據中包含子對象 |
---|---|---|---|
賦值 | 是 | 改變會使原數據一同改變 | 改變會使原數據一同改變 |
淺拷貝 | 否 | 改變「不」會使原數據一同改變 | 改變會使原數據一同改變 |
深拷貝 | 否 | 改變「不」會使原數據一同改變 | 改變「不」會使原數據一同改變 |
本文使用 mdnice 排版