对象的深拷贝与浅拷贝
前言
浅拷贝有很多种方法,扩展运算符、Object.assign
方法,直接for in
遍历拷贝。
深拷贝需要使用递归,需要额外考虑一些特殊情况,比如扩展对象Date|RegExp
的拷贝,还有可能会遇到循环引用,该如何解决。
浅拷贝
使用for in
来遍历对象,需要注意下面几点:
- for in 会遍历到继承过来的属性,所以使用
hasOwnProperty
判断一下,只拷贝自己本身的属性 - 本例中只考虑到了数组和普通对象,使用了
instanceof
判断传递过来的值
js
// 只考虑对象类型
function shallowCopy(obj) {
if (typeof obj !== 'object') return
let newObject = obj instanceof Array ? [] : {}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObject[key] = obj[key]
}
}
return newObject
}
深拷贝
简单版
只考虑普通对象,不考虑内置对象和函数。
使用递归,赋值时在判断一下类型。
js
function deepClone(obj) {
if (typeof obj !== 'object') return;
let newObj = obj instanceof Array ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
}
}
return newObj
}
复杂版
基于简单版的基础上,还考虑了内置对象比如 Date、RegExp 等对象和函数以及解决了循环引用的问题。
js
function isObject(target) {
return target !== null && (typeof target === 'object' || typeof target === 'function')
}
function getType(target) {
return Object.prototype.toString.call(target)
}
function deepClone(target, map = new WeakMap()) {
// 基本类型
if (!isObject(target)) {
return target
}
if (map.get(target)) {
return target
}
map.set(target, true)
const type = getType(target)
// 日期
if (type === '[object Date]') {
// 或者 return new target.constructor(target)
return new Date(target)
}
// 正则
if (type === '[object RegExp]') {
return new RegExp(target.source, /\w*$/.exec(target))
}
// function
if (type === '[object Function]') {
const fnStr = target.toString()
if (target.prototype) {
// function声明的函数
const index1 = fnStr.indexOf('(')
const index2 = fnStr.indexOf(')')
const index3 = fnStr.indexOf('{', index2)
const params = fnStr.slice(index1 + 1, index2)
const body = fnStr.slice(index3 + 1, -1)
if (params) {
return new Function(params, body)
} else {
return new Function(body)
}
} else {
// 箭头函数
return eval(fnStr)
}
}
let cloneTarget
// map
if (type === '[object Map]') {
cloneTarget = new Map()
target.forEach((value, key) => {
cloneTarget.set([key], deepClone(value, map))
})
}
// set
if (type === '[object Set]') {
cloneTarget = new Set()
target.forEach((value) => {
cloneTarget.add(deepClone(value, map))
})
}
// 数组
if (type === '[object Array]') {
cloneTarget = []
for (const value of target) {
cloneTarget.push(deepClone(value, map))
}
}
// 对象
if (type === '[object Object]') {
cloneTarget = {}
for (const key in target) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
cloneTarget[key] = deepClone(target[key], map)
}
}
}
return cloneTarget
}
总结
浅拷贝时:
判断传递过来的是数组还是对象,可以使用下面几种方法
Array.isArray()
判断是否是数组target instanceof Array
判断原型Object.prototype.toString.call(target) ==='[object Array]'
打印输出
使用
for in
遍历对象时,继承属性也会被遍历出来,所以在赋值时,可以使用Object.prototype.hasOwnProperty
判断一下是否是自己的属性深拷贝时,有可能会出现循环引用的情况,可以在递归的时候传递一个
weakMap
,记录下已经遍历过的对象,当再次遇到时,直接返回结果即可。判断传递的对象是否是扩展对象
Date|RegExp
,可以使用它们的constructor
属性,比如:/^(RegExp|Date)$/i.test(constructor.name)
参考
https://juejin.cn/post/6946022649768181774#heading-9https://juejin.cn/post/6844903929705136141#heading-5