项目中,我们经常需要对对象进行深拷贝。 我们一般想到的方法是使用JSON.stringify(sourceObj),此方法将对象转成字符串,在使用 JSON.parse(jsonTarget)将字符串转对象。
可以通过上面的方法实现对象的深拷贝。
但JSON.parse 和 JSON.stringify 会出现转换属性值前后的不一致性
此方式拷贝对象因为有以上这么多缺陷,所以我们不如自己封装一个属于自己的 javascript 对象深拷贝的函数,反而一劳永逸
手动封装对象深拷贝方法
对象属性的拷贝无疑就是把源对象的属性以深度遍历的方式复制到新的对象上,当遍历到一个属性值为对象类型的值时,就需要针对这个值进行再次的遍历,也是就用递归的方式遍历源对象的所有属性。让我们先看这一部分代码
function cloneDeep(obj) {const result = {}for (let key in obj) {// 判断key 是否是对象自身上的属性,以避免对象原型链上属性的拷贝if (obj.hasOwnProperty(key)) { result[key] = cloneDeep(obj[key]) //需要对属性值递归拷贝 } }}
这段代码是对象属性深拷贝的逻辑,但是不同的数据类型各自有其特殊的操作方式需要处理,下面就把这些处理边界场景的代码补充上,看看完成的代码应该是怎样的
function isPrimitiveValue(value) {
if (
typeof value === 'string' ||
typeof value === 'number' ||
value == null ||
typeof value === 'boolean' ||
Number.isNaN(value)
) {
return true
}
return false
}
function cloneDeep(value) {
// 判断拷贝的数据类型,如果为原始类型数据,直接返回其值
if (isPrimitiveValue(value)) {
return value
}
// 定义一个保存引用类型的变量,根据 引用数据类型不同的子类型初始化不同的值,以下对象类型的判断和初始化可以根据自身功能的需要做删减。这里列出了所有的引用类型的场景。
let result
if (typeof value === 'function') {
// result=value 如果复制函数的时候需要保持同一个引用可以省去新函数的创建,这里用eval创建了一个原函数的副本
result = eval(`(${value.toString()})`)
} else if (Array.isArray(value)) {
result = []
} else if (value instanceof RegExp) {
result = new RegExp(value)
} else if (value instanceof Date) {
result = new Date(value)
} else if (value instanceof Number) {
result = new Number(value)
} else if (value instanceof String) {
result = new String(value)
} else if (value instanceof Boolean) {
result = new Boolean(value)
} else if (typeof value === 'object') {
result = new Object()
}
for (let key in value) {
if (value.hasOwnProperty(key)) {
try {
result[key] = cloneObject(value[key]) //属性值为原始类型包装对象的时候,(Number,String,Boolean)这里会抛错,需要加一个错误处理,对运行结果没有影响。
} catch (error) {
// console.error(error)
}
}
}
return result
}
代码中首先封装了一个判断数据是否是原始类型的方法,这里只是为了保持 cloneDeep 函数的功能干净,其实你也可以完全放到一块,这个完全取决于自己的编码风格。如果在业务上需要有多处判断数据是原始类型还是引用类型的场景时,以上这种代码功能抽离的方式就方便处理了 参考:https://juejin.im/post/5e892e0251882573ba207c2e