手写深拷贝
注意: Object.assign 不是深拷贝!只拷贝了一层
以下为简易版
/**
* 深拷贝
*/
const obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用!!!
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
实现一个完整的深拷贝如下:
typeof 运算符
- 识别所有值类型
- 识别函数
- 判断是否是引用类型(不可再细分)
- typeof null === 'object'
JSON.stringfy() 实现深拷贝存在的问题:
- 执行会报错:存在BigInt类型、循环引用。
- 拷贝Date引用类型会变成字符串。
- 键值会消失:对象的值中为Function、Undefined、Symbol 这几种类型,。
- 键值变成空对象:对象的值中为Map、Set、RegExp这几种类型。
- 无法拷贝:不可枚举属性、对象的原型链。
- 补充:其他更详细的内容请查看官方文档:JSON.stringify() - JavaScript | MDN (mozilla.org)
深拷贝需要考虑的问题:
- 基本类型数据是否能拷贝?
- 键和值都是基本类型的普通对象是否能拷贝?
- Symbol作为对象的key是否能拷贝?
- Date和RegExp对象类型是否能拷贝?
- Map和Set对象类型是否能拷贝?
- Function对象类型是否能拷贝?(函数我们一般不用深拷贝)
- 对象的原型是否能拷贝?
- 不可枚举属性是否能拷贝?
- 循环引用是否能拷贝?
完整版实现:
JavaScript深拷贝看这篇就行了!(实现完美的ES6+版本)码飞CC的博客-CSDN博客
* 深拷贝完整版实现
* @param {Object} target
* @returns
*/
function deepClone(target = {}) {
// WeakMap作为记录对象的hash表 (用于防止循环引用)
const map = new WeakMap()
// 工具函数
function isObject(target) {
return (typeof target === 'object') || typeof target === 'function'
}
// 拷贝主逻辑
function clone(data) {
// 基础类型直接返回
if (!isObject(data)) {
return data
}
// 日期或者正则对象,则直接构造一个新的对象返回
if ([Date, RegExp].includes(data.constructor)) {
return new data.constructor(data)
}
// 处理函数对象
if (typeof data === 'function') {
return new Function('retuen' + data.toString())()
}
// 如果对象已经存在,则直接返回该对象
const exist = map.get(data)
if (exist) {
return exist
}
// 处理 Map 对象
if (data instanceof Map) {
const result = new Map()
map.set(data, result)
data.forEach((val, key) => {
// 注意:map种的值为 object 的话也得深拷贝
if (isObject(val)) {
result.set(key.clone(val))
} else {
result.set(key, val)
}
})
return result
}
// 处理 Set 对象
if (data instanceof Set) {
const res = new Set()
map.set(data, res)
data.forEach(val => {
if (isObject(val)) {
res.add(clone(val))
} else {
res.add(val)
}
})
return res
}
// 收集键名(考虑以Symbol作为key以及不可枚举的属性)
// Reflect.ownKeys(obj)相当于[...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)]
const keys = Reflect.ownKeys(data)
// 获得对象的所有属性以及对应的属性描述
const allDesc = Object.getOwnPropertyDescriptors(data)
// 创建一个新对象, 继承传入对象的原型链、(浅拷贝)
const result = Object.create(Object.getPrototypeOf(data), allDesc)
// 新对象加入map中,进行记录
map.set(data, result)
// Object.create() 是浅拷贝,所以要判断值的类型并递归进行深拷贝
keys.forEach(key => {
const val = data[key]
if (isObject(key)) {
result[key] = clone(val)
} else {
result[key] = val
}
})
return result
}
return clone(data)
}