一. 深拷貝 / 淺拷貝
1. 淺拷貝: 指針拷貝, 讓拷貝前和拷貝后對象的指針指向同一塊內(nèi)存地址
- 增加了原對象的引用計數(shù)
- 沒有新的內(nèi)存分配
let obj1 = {
a: 1,
b: 2
}
let obj2 = obj1
obj1.a = 3
console.log(obj2.a) // 3
2. 深拷貝
- Object.assign(),只能拷貝一層, 且會改變原有對象 - ( 數(shù)組 concat 方法不會改變原有數(shù)組 )
- ES6 擴展運算符 { ...obj },同樣只能拷貝一層
- immutable.js 實現(xiàn),利用其不可變數(shù)據(jù)集的特性,省去深拷貝環(huán)節(jié)每次改變只影響當(dāng)前節(jié)點和它的父節(jié)點, 其他節(jié)點復(fù)用,效率高
- lodash 庫的 cloneDeep 方法實現(xiàn)深拷貝
- 遍歷 for in / Object.keys(obj) 遞歸, 代碼以及優(yōu)缺點如下:
1). JSON.parse(JSON.stringify()) 實現(xiàn)深拷貝的不足
- 如果 obj 里面有時間對象,則 JSON.stringify 后再 JSON.parse 的結(jié)果,時間將只是字符串的形式。而不是時間對象;
const obj = {
date: new Date()
}
const newObj = JSON.parse(JSON.stringify(obj))
console.log(Object.prototype.toString.call(obj.date)) // [object Date]
console.log(Object.prototype.toString.call(newObj.date)) // [object String]
console.log(newObj.date) // 2021-04-09T02:12:31.440Z
- 如果 obj 里有 RegExp、Error 對象,則序列化的結(jié)果將只得到空對象;
const obj = {
date: new Date(),
regExp: new RegExp('\d'),
error: new Error('error')
}
const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj.regExp) // {}
console.log(newObj.error) // {}
- 如果 obj 鍵名有 Symbol 或者值里有函數(shù),undefined,則序列化的結(jié)果會丟失;
const obj = {
date: new Date(),
regExp: new RegExp('\d'),
error: new Error('error'),
fn: function() {
console.log('function')
},
und: undefined
}
const sym = Symbol('sym')
test[sym] = 'sym'
const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj) // {date: "2021-04-10T01:47:04.806Z", regExp: {…}, error: {…}}
- 如果 obj 里有 NaN、Infinity 和 -Infinity,則序列化的結(jié)果會變成 null
const obj = {
date: new Date(),
regExp: new RegExp('\d'),
error: new Error('error'),
fn: function() {
console.log('function')
},
und: undefined,
nan: NaN,
infi: Infinity
}
const newObj = JSON.parse(JSON.stringify(obj))
console.log(Object.prototype.toString.call(obj.date))
console.log(Object.prototype.toString.call(newObj.date))
console.log(newObj.nan) // null
console.log(newObj.infi) // null
- JSON.stringify() 只能序列化對象的可枚舉的自有屬性,例如 如果 obj 中的對象是有構(gòu)造函數(shù)生成的, 則使用 JSON.parse(JSON.stringify(obj)) 深拷貝后,會丟棄對象的 constructor;
function Person(name) {
this.name = name
}
const zs = new Person('zhangsan')
const obj = {
date: new Date(),
regExp: new RegExp('\d'),
error: new Error('error'),
fn: function() {
console.log('function')
},
und: undefined,
nan: NaN,
infi: Infinity,
zs
}
const newObj = JSON.parse(JSON.stringify(obj))
console.log(obj.zs)
console.log(newObj.zs)

compare
- 如果對象中存在循環(huán)引用的情況也無法正確實現(xiàn)深拷貝;

對象循環(huán)引用
2). Reflect 遞歸實現(xiàn)
function deepClone(obj) {
// 判斷如果 obj 是基本類型數(shù)據(jù)或者函數(shù), 直接返回
if (!obj || typeof obj !== 'object') return obj;
if (obj instanceof Error) {
return new Error(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
if (obj instanceof Date) {
return new Date(obj);
}
const result = Array.isArray(obj) ? [] : {};
Reflect.ownKeys(obj).forEach(item => {
if (obj[item] && typeof obj[item] === 'object') {
result[item] = deepClone(obj[item]);
} else {
result[item] = obj[item];
}
})
return result;
}
// 測試
const a = '111'
console.log(deepClone(a))
const test = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: {
name: '我是一個對象',
num: 1,
id: 1
},
arr: [0, 1, 2],
func: function() {
console.log('我是一個函數(shù)')
},
date: new Date(0),
reg: new RegExp('我是一個正則', 'ig'),
err: new Error('error')
}
const sym = Symbol('我是一個Symbol')
test[sym] = 'Symbol'
console.log(deepClone(test))
console.log(deepClone(test.date))
console.log(deepClone(test.reg))
console.log(deepClone(test.err))

Reflect_deepClone_result
- 遞歸方法深拷貝互相引用的對象會造成棧溢出