原文地址:JavaScript基礎心法——深淺拷貝javascript
歡迎star。java
若是有錯誤的地方歡迎指正。git
淺拷貝和深拷貝都是對於JS中的引用類型而言的,淺拷貝就只是複製對象的引用,若是拷貝後的對象發生變化,原對象也會發生變化。只有深拷貝纔是真正地對對象的拷貝。github
說到深淺拷貝,必須先提到的是JavaScript的數據類型,以前的一篇文章JavaScript基礎心法——數據類型說的很清楚了,這裏就很少說了。數組
須要知道的就是一點:JavaScript的數據類型分爲基本數據類型和引用數據類型。app
對於基本數據類型的拷貝,並無深淺拷貝的區別,咱們所說的深淺拷貝都是對於引用數據類型而言的。函數
淺拷貝的意思就是隻複製引用,而未複製真正的值。測試
const originArray = [1,2,3,4,5]; const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; const cloneArray = originArray; const cloneObj = originObj; console.log(cloneArray); // [1,2,3,4,5] console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}} cloneArray.push(6); cloneObj.a = {aa:'aa'}; console.log(cloneArray); // [1,2,3,4,5,6] console.log(originArray); // [1,2,3,4,5,6] console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}} console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
上面的代碼是最簡單的利用 =
賦值操做符實現了一個淺拷貝,能夠很清楚的看到,隨着 cloneArray
和 cloneObj
改變,originArray
和 originObj
也隨着發生了變化。code
深拷貝就是對目標的徹底拷貝,不像淺拷貝那樣只是複製了一層引用,就連值也都複製了。對象
只要進行了深拷貝,它們老死不相往來,誰也不會影響誰。
目前實現深拷貝的方法很少,主要是兩種:
JSON
對象中的 parse
和 stringify
先看看這兩個方法吧:
The JSON.stringify() method converts a JavaScript value to a JSON string.
JSON.stringify
是將一個 JavaScript
值轉成一個 JSON
字符串。
The JSON.parse() method parses a JSON string, constructing the JavaScript value or object described by the string.
JSON.parse
是將一個 JSON
字符串轉成一個 JavaScript
值或對象。
很好理解吧,就是 JavaScript
值和 JSON
字符串的相互轉換。
它能實現深拷貝呢?咱們來試試。
const originArray = [1,2,3,4,5]; const cloneArray = JSON.parse(JSON.stringify(originArray)); console.log(cloneArray === originArray); // false const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; const cloneObj = JSON.parse(JSON.stringify(originObj)); console.log(cloneObj === originObj); // false cloneObj.a = 'aa'; cloneObj.c = [1,1,1]; cloneObj.d.dd = 'doubled'; console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}}; console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
確實是深拷貝,也很方便。可是,這個方法只能適用於一些簡單的狀況。好比下面這樣的一個對象就不適用:
const originObj = { name:'axuebin', sayHello:function(){ console.log('Hello World'); } } console.log(originObj); // {name: "axuebin", sayHello: ƒ} const cloneObj = JSON.parse(JSON.stringify(originObj)); console.log(cloneObj); // {name: "axuebin"}
發如今 cloneObj
中,有屬性丟失了。。。那是爲何呢?
在 MDN
上找到了緣由:
If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).
undefined
、function
、symbol
會在轉換過程當中被忽略。。。
明白了吧,就是說若是對象中含有一個函數時(很常見),就不能用這個方法進行深拷貝。
遞歸的思想就很簡單了,就是對每一層的數據都實現一次 建立對象->對象賦值
的操做,簡單粗暴上代碼:
function deepClone(source){ const targetObj = source.constructor === Array ? [] : {}; // 判斷複製的目標是數組仍是對象 for(let keys in source){ // 遍歷目標 if(source.hasOwnProperty(keys)){ if(source[keys] && typeof source[keys] === 'object'){ // 若是值是對象,就遞歸一下 targetObj[keys] = source[keys].constructor === Array ? [] : {}; targetObj[keys] = deepClone(source[keys]); }else{ // 若是不是,就直接賦值 targetObj[keys] = source[keys]; } } } return targetObj; }
咱們來試試:
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; const cloneObj = deepClone(originObj); console.log(cloneObj === originObj); // false cloneObj.a = 'aa'; cloneObj.c = [1,1,1]; cloneObj.d.dd = 'doubled'; console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}}; console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
能夠。那再試試帶有函數的:
const originObj = { name:'axuebin', sayHello:function(){ console.log('Hello World'); } } console.log(originObj); // {name: "axuebin", sayHello: ƒ} const cloneObj = deepClone(originObj); console.log(cloneObj); // {name: "axuebin", sayHello: ƒ}
也能夠。搞定。
是否是覺得這樣就完了?? 固然不是。
咱們知道在 JavaScript
中,數組有兩個方法 concat
和 slice
是能夠實現對原數組的拷貝的,這兩個方法都不會修改原數組,而是返回一個修改後的新數組。
同時,ES6 中 引入了 Object.assgn
方法和 ...
展開運算符也能實現對對象的拷貝。
那它們是淺拷貝仍是深拷貝呢?
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
該方法能夠鏈接兩個或者更多的數組,可是它不會修改已存在的數組,而是返回一個新數組。
看着這意思,很像是深拷貝啊,咱們來試試:
const originArray = [1,2,3,4,5]; const cloneArray = originArray.concat(); console.log(cloneArray === originArray); // false cloneArray.push(6); // [1,2,3,4,5,6] console.log(originArray); [1,2,3,4,5];
看上去是深拷貝的。
咱們來考慮一個問題,若是這個對象是多層的,會怎樣。
const originArray = [1,[1,2,3],{a:1}]; const cloneArray = originArray.concat(); console.log(cloneArray === originArray); // false cloneArray[1].push(4); cloneArray[2].a = 2; console.log(originArray); // [1,[1,2,3,4],{a:2}]
originArray
中含有數組 [1,2,3]
和對象 {a:1}
,若是咱們直接修改數組和對象,不會影響 originArray
,可是咱們修改數組 [1,2,3]
或對象 {a:1}
時,發現 originArray
也發生了變化。
結論:concat
只是對數組的第一層進行深拷貝。
The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.
解釋中都直接寫道是 a shallow copy
了 ~
可是,並非!
const originArray = [1,2,3,4,5]; const cloneArray = originArray.slice(); console.log(cloneArray === originArray); // false cloneArray.push(6); // [1,2,3,4,5,6] console.log(originArray); [1,2,3,4,5];
一樣地,咱們試試多層的數組。
const originArray = [1,[1,2,3],{a:1}]; const cloneArray = originArray.slice(); console.log(cloneArray === originArray); // false cloneArray[1].push(4); cloneArray[2].a = 2; console.log(originArray); // [1,[1,2,3,4],{a:2}]
果真,結果和 concat
是同樣的。
結論:slice
只是對數組的第一層進行深拷貝。
The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
複製複製複製。
那究竟是淺拷貝仍是深拷貝呢?
本身試試吧。。
結論:Object.assign()
拷貝的是屬性值。假如源對象的屬性值是一個指向對象的引用,它也只拷貝那個引用值。
const originArray = [1,2,3,4,5,[6,7,8]]; const originObj = {a:1,b:{bb:1}}; const cloneArray = [...originArray]; cloneArray[0] = 0; cloneArray[5].push(9); console.log(originArray); // [1,2,3,4,5,[6,7,8,9]] const cloneObj = {...originObj}; cloneObj.a = 2; cloneObj.b.bb = 2; console.log(originObj); // {a:1,b:{bb:2}}
結論:...
實現的是對象第一層的深拷貝。後面的只是拷貝的引用值。
咱們知道了,會有一種狀況,就是對目標對象的第一層進行深拷貝,而後後面的是淺拷貝,能夠稱做「首層淺拷貝」。
咱們能夠本身實現一個這樣的函數:
function shallowClone(source) { const targetObj = source.constructor === Array ? [] : {}; // 判斷複製的目標是數組仍是對象 for (let keys in source) { // 遍歷目標 if (source.hasOwnProperty(keys)) { targetObj[keys] = source[keys]; } } return targetObj; }
咱們來測試一下:
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; const cloneObj = shallowClone(originObj); console.log(cloneObj === originObj); // false cloneObj.a='aa'; cloneObj.c=[1,1,1]; cloneObj.d.dd='surprise';
通過上面的修改,cloneObj
不用說,確定是 {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}}
了,那 originObj
呢?剛剛咱們驗證了 cloneObj === originObj
是 false
,說明這兩個對象引用地址不一樣啊,那應該就是修改了 cloneObj
並不影響 originObj
。
console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}} console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'surprise'}}
What happend?
originObj
中關於 a
、c
都沒被影響,可是 d
中的一個對象被修改了。。。說好的深拷貝呢?不是引用地址都不同了嗎?
原來是這樣:
shallowClone
的代碼中咱們能夠看出,咱們只對第一層的目標進行了 深拷貝
,而第二層開始的目標咱們是直接利用 =
賦值操做符進行拷貝的。=
實現的是淺拷貝,只拷貝對象的引用值;JSON.stringify
實現的是深拷貝,可是對目標對象有要求;