問題緣起
如果給一個變量賦值一個對象,那么新變量和原對象變量將會是同一個引用,其中一方改變,另一方也會改變。
該問題可以用淺拷貝來解決。但是淺拷貝只能解決對象的第一層的引用問題(或數組的第一維),如果接下去的屬性還是對象的話那么還是同一個引用。這就需要引入深拷貝。
對象的深淺拷貝
1. 淺拷貝: Object.assign({},sourceObj) 或 {...sourceObj}
當sourceObj的屬性是值的時候,效果和深拷貝看起來一樣;但是如果sourceObj的屬性是另外一個對象時,就是淺拷貝了。
(1)使用Object.assign()實現淺拷貝
let a = {
age:1
}
let b = Object.assign({}, a);
a.age = 2;
console.log(b.age);//1
(2)使用{...obj}實現淺拷貝
let a = {
age: 1
}
let b = {...a};
a.age = 2;
console.log(b.age);//1
2. 深拷貝:JSON.parse(JSON.stringify(sourceObj)) 或loadash庫的deepClone()方法
上面已經說過,淺拷貝只能解決第一層的引用問題,如果屬性還是一個對象,那么還是會共享一個引用:
let a = {
age: 1,
jobs: {
main:'frontend'
}
}
let b = {...a};
a.jobs.main = 'backend';
console.log(b.jobs.main);//'backend'
該問題通常可以用JSON.parse(JSON.stringify(sourceObj))或lodash的cloneDeep(Obj)方法來解決
(1)使用JSON.parse(JSON.stringify(sourceObj))實現深拷貝
let a = {
age: 1,
jobs: {
main:'frontend'
}
}
let b = JSON.parse(JSON.stringify(a));
a.jobs.main = 'backend';
console.log(b.jobs.main);//'frontend'
JSON.parse(JSON.stringify(sourceObj))實現深拷貝的局限:
- 會忽略undefined
- 不能處理函數,會忽略掉函數
- 不能處理循環引用的對象,會報錯TypeError
(2)使用loadash的deepClone()方法
如果數據中出現了以上3中情況,那么可以考慮使用loadash的深拷貝函數。
參考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
數組的深淺拷貝
1.淺拷貝:arr.slice()或arr.concat():
淺拷貝,即到數組只有一維的時候,且每一項不是對象的話這樣拷貝看起來和深拷貝效果一樣
(1) 使用Array.prototype.slice()實現淺拷貝
const a = [1,2,3]
const b = a.slice();
a.push(4);
console.log(a);//[1,2,3,4]
console.log(b);//[1,2,3]
淺拷貝一個數組,這樣操作新數組時,就不會改變原數組。
slice不修改原數組,只返回一個淺復制了原數組總的元素的一個新數組。原數組的元素會按照下述規則拷貝:
- 如果該元素是個對象引用 (不是實際的對象),slice 會拷貝這個對象引用到新的數組里。兩個對象引用都引用了同一個對象。如果被引用的對象發生改變,則新的和原來的數組中的這個元素也會發生改變。
- 對於字符串、數字及布爾值來說(不是 String、Number 或者 Boolean 對象),slice 會拷貝這些值到新的數組里。在別的數組里修改這些字符串或數字或是布爾值,將不會影響另一個數組。
如果向兩個數組任一中添加了新元素,則另一個不會受到影響。
(2)使用 Array.prototype.concat()實現淺拷貝
const a = [1,2,3]
const b = a.concat();
a.push(4);
console.log(a);//[1,2,3,4]
console.log(b);//[1,2,3]
concat方法創建一個新的數組,它由被調用的對象中的元素組成,每個參數的順序依次是該參數的元素(如果參數是數組)或參數本身(如果參數不是數組)。它不會遞歸到嵌套數組參數中。
concat方法不會改變this或任何作為參數提供的數組,而是返回一個淺拷貝,它包含與原始數組相結合的相同元素的副本。 原始數組的元素將按照如下規則復制到新數組中:
- 對象引用(而不是實際對象):concat將對象引用復制到新數組中。 原始數組和新數組都引用相同的對象。 也就是說,如果引用的對象被修改,則更改對於新數組和原始數組都是可見的。 這包括也是數組的數組參數的元素。
- 數據類型如字符串,數字和布爾(不是String,Number 和 Boolean 對象):concat將字符串和數字的值復制到新數組中。
2. 深拷貝:JSON.parse(JSON.stringify(arr)) 或loadash的deepClone()方法
同對象的深拷貝。
引申閱讀:拷貝不變性的重要意義
參見https://reactjs.org/tutorial/tutorial.html#why-immutability-is-important
參考資料:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
https://github.com/wengjq/Blog/issues/3
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
