深拷贝与浅拷贝
浅拷贝
什么是浅拷贝?举个例子:
const a = {
b: 1,
}
const c = a
console.log(c === a) // true
c.b = 2
console.log(a.b) // 2
我们先将 a
赋值给 c
,实际上是将 a
的内存地址分享给 c
,两个变量引用的实际上是同一个对象
所以当改变 c
的 b
属性时,a
的 b
属性也会跟着改变
由上可得:浅拷贝只复制对象的第一层属性
深拷贝
由于浅拷贝所得到的值的内存地址是一样的,所以要想改变属性值而不影响其他对象,就需要深拷贝
深拷贝可以将对象的属性进行递归复制
function deepCopy(oldObj, newObj) {
// 判断newObj是否传入,如果没有初始化一个空对象
newObj = newObj || {}
// for...in循环遍历oldObj的key
for (let item in oldObj) {
// 判断是否为对象
if (typeof oldObj[item] === 'object') {
// 判断属性值是否为数组
if (objObj[item].constructor === Array) {
newObj[item] = []
} else {
// 如果不是数组,那就是对象
newObj[item] = {}
}
// 递归调用deepCopy
deepCopy(oldObj[item], newObj[item])
} else {
newObj[item] = oldObj[item]
}
}
return newObj
}
这种方法比较麻烦,实际项目中可以使用序列化反序列化的方式实现深拷贝:
const deepCopy = (oldObj) => JSON.parse(JSON.stringify(oldObj))
const a = {
b: 1,
c: [1, 2, 3],
d: {
e: 1,
},
}
const f = deepCopy(a)
f.b = 2
f.c[0] = 4
f.d.e = 5
console.log(a)
// {
// b: 1,
// c: [1, 2, 3],
// d: {
// e: 1
// }
// }
console.log(f)
// {
// b: 2,
// c: [4, 2, 3],
// d: {
// e: 5
// }
// }
可以看到这种方法也可以实现深拷贝。
Notice
这种方式实现的深拷贝有如下几个缺点:
- 如果对象中存在时间对象会被转换成字符串。
- 如果对象中里有 RegExp、Error 对象,则序列化的结果将只得到空对象。
- 如果对象中有函数,undefined,则序列化的结果会把函数,
undefined
丢失。 - 如果对象中有NaN、Infinity 和 -Infinity,则序列化的结果会变成
null
。 - 如果对象中存在循环引用的情况也无法正确实现深拷贝。
Object.assign
Object.assign
是 ES6 规范中的一个新方法。
也可以实现对象的拷贝,但仅限于第一层的深拷贝,第一层之后一律是浅拷贝:
const deepCopy = (oldObj) => Object.assign({}, oldObj)
const a = {
b: 1,
c: [1, 2, 3],
d: {
e: 1,
},
}
const f = deepCopy(a)
f.b = 2
f.c[0] = 4
f.d.e = 5
console.log(a)
// {
// b: 1,
// c: [4, 2, 3],
// d: {
// e: 5
// }
// }
console.log(f)
// {
// b: 2,
// c: [4, 2, 3],
// d: {
// e: 5
// }
// }
使用 Object.assign
方法并不能达到严格意义上的深拷贝,只能深拷贝第一层的属性而已,因此,如果对象有多层嵌套,就不要用 Object.assign
进行深拷贝
对象扩展符
对象扩展符是 ES7 的标准
还是一样的例子,来看看对象扩展符能否实现深拷贝:
const deepCopy = (oldObj) => ({ ...oldObj })
const a = {
b: 1,
c: [1, 2, 3],
d: {
e: 1,
},
}
const f = deepCopy(a)
f.b = 2
f.c[0] = 4
f.d.e = 5
console.log(a)
// {
// b: 1,
// c: [4, 2, 3],
// d: {
// e: 5
// }
// }
console.log(f)
// {
// b: 2,
// c: [4, 2, 3],
// d: {
// e: 5
// }
// }
结果和 Object.assign
一模一样,同样也只是实现了第一层的深拷贝
深拷贝实现
最近实现了一个比较全面的深拷贝代码:
function deepClone(origin, target, hash = new WeakMap()) {
//origin:要被拷贝的对象
// 需要完善,克隆的结果和之前保持相同的所属类
const target = target || {}
// 处理特殊情况
if (origin == null) return origin //null 和 undefined 都不用处理
if (origin instanceof Date) return new Date(origin)
if (origin instanceof RegExp) return new RegExp(origin)
if (typeof origin !== 'object') return origin // 普通常量直接返回
// 防止对象中的循环引用爆栈,把拷贝过的对象直接返还即可
if (hash.has(origin)) return hash.get(origin)
hash.set(origin, target) // 制作一个映射表
// 拿出所有属性,包括可枚举的和不可枚举的,但不能拿到symbol类型
const props = Object.getOwnPropertyNames(origin)
props.forEach((prop, index) => {
if (origin.hasOwnProperty(prop)) {
if (typeof origin[prop] === 'object') {
if (Object.prototype.toString.call(origin[prop]) == '[object Array]') {
//数组
target[prop] = []
deepClone(origin[prop], target[prop], hash)
} else if (Object.prototype.toString.call(origin[prop]) == '[object Object]') {
//普通对象
target[prop] = {}
deepClone(origin[prop], target[prop], hash)
} else if (origin[prop] instanceof Date) {
// 处理日期对象
target[prop] = new Date(origin[prop])
} else if (origin[prop] instanceof RegExp) {
// 处理正则对象
target[prop] = new RegExp(origin[prop])
} else {
//null
target[prop] = null
}
} else if (typeof origin[prop] === 'function') {
const _copyFn = function (fn) {
const result = new Function('return ' + fn)()
for (let i in fn) {
deepClone[(fn[i], result[i], hash)]
}
return result
}
target[prop] = _copyFn(origin[prop])
} else {
//除了object、function,剩下都是直接赋值的原始值
target[prop] = origin[prop]
}
}
})
// 单独处理symbol
const symKeys = Object.getOwnPropertySymbols(origin)
if (symKeys.length) {
symKeys.forEach((symKey) => {
target[symKey] = origin[symKey]
})
}
return target
}
结论
JSON.parse(JSON.stringify(oldObj))
这种方法可以实现严格意义上的深拷贝,但有许多缺陷。Object.assign
和对象扩展符只能对第一层属性深拷贝,更深层的属性只能浅拷贝。