一、前言 hello,大家好~ ,本文主要介紹在 JavaScript 中什麼是深拷貝和淺拷貝,以及如何實現一個對象的深拷貝。 二、隨處可見的 “賦值” 在 JavaScript 中我們最常見的操作之一是將一個變數的值賦值給另一個變數,這個過程我們也可以稱為 “拷貝” 一份變數的值給另一個變數。 2 ...
一、前言
hello,大家好~ ,本文主要介紹在 JavaScript 中什麼是深拷貝和淺拷貝,以及如何實現一個對象的深拷貝。
二、隨處可見的 “賦值”
在 JavaScript 中我們最常見的操作之一是將一個變數的值賦值給另一個變數,這個過程我們也可以稱為 “拷貝” 一份變數的值給另一個變數。
2.1 基本數據類型的賦值操作
在涉及到基本數據類型(string、Boolean、number...)賦值操作的時候,原始變數值被覆制給另外一個變數。我們來考慮下下麵這段代碼:我們將值為 10 的變數 x 賦值給變數 y,此時 x 和 y 已經失去“聯繫”了,所以改變 x 的值不會影響 y 的值,最後輸出 20 和 10。
<script>
let x = 10;
let y = x;
x += 10;
console.log(x, y); // 20 10
</script>
2.2 引用數據類型的賦值操作
與基本數據類型賦值相反,引用數據類型(Object、Array...)在賦值的時候傳遞的只是一個 引用 (原始值和拷貝賦值後的值指向同一塊記憶體空間) ,所以當被覆制出來的對象值改變的時候,原始的對象值也會跟著改變,這種現象被稱為 淺拷貝 。
對象賦值案例: 將 user 對象賦值給 user2 變數,修改 user2 屬性值後 user 屬性值也會跟著變,兩者的值始終保持同步,這便是淺拷貝帶來的 副作用 ,拷貝出來的值和原始值始終保持著關聯。基本數據類型的賦值沒有這種現象,所以說 淺拷貝 和 深拷貝 是相對引用數據類型而言的。
<script>
let user = {
name: 'zjl712',
age: 18,
local: 'shanghai'
}
let user2 = user;
user2.name = 'zjl';
console.log('user:', user, 'user2:', user2);
</script>
三、實現對象的深拷貝
通過以上的介紹我們知道什麼是 淺拷貝 以及淺拷貝帶來的 副作用 ,為了消除這種“副作用”我們需要對對象進行 深拷貝 。下麵將介紹一些對象拷貝的方法,它們各有優缺點。
- "=" 號直接賦值
通過等號(=)將原始值賦值給一個新的值,優點是簡便快捷,缺點 是只能進行 淺拷貝
let obj = {name:'zjl712', age:18}
let obj2 = obj;
console.log(obj == obj2) //true
- Object.assign()
通過 Object 的 assign 方法可以對對象進行深拷貝,缺點是不能對含有多層嵌套結構的對象進行深拷貝。
深拷貝單層結構的對象,改變拷貝對象的值不會改變原始對象的值。
<script>
let obj = {
name: 'zjl712',
age: 18,
getName:function(){
return this.name
}
}
let obj2 = Object.assign({}, obj)
console.log(obj == obj2); // false
obj2.name = 'zjl'
console.log(obj, obj2);
</script>
嘗試拷貝多層嵌套結構對象,改變拷貝對象內嵌套的對象值(obj2.local.nickname)後,原始對象嵌套的值也會跟著改變。改變拷貝對象非嵌套的值,原始對象的值不會跟著變。所以 Object.assign() 只能實現部分深拷貝。
<script>
let obj = {
name: 'zjl712',
age: 18,
getName:function(){
return this.name
},
local: {
name: 'shanghai',
nickname: 'modu',
}
}
let obj2 = Object.assign({}, obj)
console.log(obj == obj2); // false
obj2.local.nickname = '魔都'
obj2.name = 'zjl'
console.log(obj, obj2);
console.log(obj.local == obj2.local) // true
</script>
- JSON.stringify() 和 JSON.parse()
JSON.stringify() 方法接收一個對象作為參數,將對象轉為 JSON 字元串。JSON.parse() 接收一個 JSON 字元串,將該字元串轉為一個對象。結合這兩個方法可以對一個對象進行深拷貝。
結合以上兩個方法對對象進行深拷貝
let obj = {name: 'zjl712',age: 18}
let obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj == obj2); // false
obj2.name = 'zjl'
console.log(obj, obj2);
當對象屬性值含有 函數(function) 時,且含有嵌套對象時。拷貝的值 屬性值為函數的屬性會丟失 。優點是能對多層嵌套對象進行深拷貝。
let obj = {
name: 'zjl712',
age: 18,
getName:function(){
return this.name
},
local: {
name: 'shanghai',
nickname: 'modu',
}
}
let obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj == obj2); // false
obj2.local.nickname = '魔都'
obj2.name = 'zjl'
console.log(obj, obj2);
console.log(obj.local == obj2.local) // false
- 對象擴展符(...)
使用 ES6 的擴展操作符,使用三個點(...)將原始對象的值拷貝給另一個對象。然而三點擴展符和 Object.assign() 很相似,擴展符不能對多層嵌套對象進行深拷貝。
let obj = {
name: 'zjl712',
age: 18,
getName:function(){
return this.name
},
local: {
name: 'shanghai',
nickname: 'modu',
}
}
let obj2 = {...obj}
console.log(obj == obj2); // false
obj2.local.nickname = '魔都'
obj2.name = 'zjl'
console.log(obj, obj2);
- 遞歸的方式實現對象的深拷貝
實現含有嵌套結構、函數對象的深拷貝以上方法都無能為力,我們可以用遞歸的方法完成對象的深拷貝。缺點是對複雜的對象(Buffer、Date 等實例對象)無法實現深拷貝
function myDeepClone(obj){
let clone;
// 排除非引用類型數據
if(obj == null || typeof obj != 'object') return obj;
if(Array.isArray(obj)){
// obj 是數組
clone = new obj.constructor(obj.length)
obj.forEach((value, index) => {
clone[index] = typeof value === 'object'?myDeepClone(value):value
})
}else{
// 淺拷貝一份原始數據
clone = Object.assign({}, obj)
// 遞歸 clone 內的每一個屬性值
Object.keys(clone).forEach(key => {
clone[key] = typeof obj[key] === 'object'?myDeepClone(obj[key]):obj[key]
})
}
return clone;
}
以上遞歸代碼有相似代碼(兩個 forEach 內的代碼),可簡化為以下代碼:
function myDeepClone(obj){
let clone;
if(obj == null || typeof obj != 'object') return obj;
clone = Object.assign({}, obj)
Object.keys(clone).forEach(key => {
clone[key] = typeof obj[key] === 'object'?myDeepClone(obj[key]):obj[key]
})
if(Array.isArray(obj)){
clone.length = obj.length
clone = Array.from(clone)
}
return clone
}
- 以上的自定義遞歸方法不夠全面,但對於基本的對象( {} )和數組( [] )深拷貝還是夠用的,要想實現複雜對象的深拷貝可以使用 lodash 的 cloneDeep 方法,這個方法實現深拷貝是比較完美的。
// 安裝 lodash :npm i lodash
// 使用
let lang = require('lodash/lang')
let clone = lang.cloneDeep(obj)
路很遠,但從未止步