前置了解
当对象的属性是 object 类型时,其实该属性保存的只是 object 的内存地址,除非将该属性重新指向了其他 object,否则对该属性的所有操作,都会通过该地址找到真正的内存区域并在其中操作。
浅拷贝
浅拷贝: object 类型的属性保存的是该 object 的内存地址,所以复制到目标对象的也是该 object 的内存地址,代表着源对象和目标对象的该属性指向的是同一块内存地址,因为对 object 属性的所有操作都是在其保存地址的内存区域内进行,所以源对象和目标对象的 object 类型属性会互相影响。
浅拷贝方式:浅拷贝的方式有很多, 我个人比较喜欢用 Object.assign(target, source) 或 扩展符的方式。
1. 适合浅拷贝的场景
源对象的属性全部是基本类型时, 适合浅拷贝。
上面浅拷贝演示过程的对应代码:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>拷贝</title>
</head>
<body>
<script>
// 源对象
let source = {
name: 'ares5k',
age: 27
}
// 目标对象
let target = {}
// 浅拷贝
Object.assign(target, source)
</script>
</body>
</html>
2. 不适合浅拷贝的场景
源对象的属性包含 object 类型,且该 object 的属性变化时不想让源对象和目标对象互相影响,这种场合不适合浅拷贝。
演示过程分析:
- 源对象的 info 属性是 object 类型, 所以 info 属性保存的是该 object 的内存地址 0x0002。
- 浅拷贝后,目标对象复制过来的也是该 object 的内存地址 0x0002。
- 对 info 属性的 sex 属性进行操作时,实际上是在内存地址为 0x0002 的内存区域上进行的。
- 因为源对象和目标对象的 info 属性保存的都是 0x0002,所以源对象和目标对象都会收到影响。
上面浅拷贝演示过程的对应代码:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>拷贝</title>
</head>
<body>
<script>
// 源对象
let source = {
name: 'ares5k',
info: {
sex: '男'
}
}
// 浅拷贝
let target = {...source}
target.name = '修改性名'
target.info.sex = '修改性别'
// 比较
console.log(source.name === target.name) // 结果: false,基本类型修改不会影响源对象
console.log(source.info.sex === target.info.sex) // 结果: true,对象类型修改会影响源对象
</script>
</body>
</html>
深拷贝
源对象的属性中,有 object 类型时,适合使用深拷贝。
在此介绍两种深拷贝方式,一个是利用内置的 JSON 序列化函数,另一个是自定义深克隆函数。
1. 内置的 JSON 序列化函数
缺点:不能复制函数,适合只有属性的场合
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>拷贝</title>
</head>
<body>
<script>
// 源对象
let source = {
name: 'ares5k',
info: {
sex: '男'
},
say(){
console.log('Hello!')
}
}
// 深克隆
let target = JSON.parse(JSON.stringify(source))
target.name = '修改性名'
target.info.sex = '修改性别'
// 比较
console.log(source.name === target.name) // 结果: false,基本类型修改不会影响源对象
console.log(source.info.sex === target.info.sex) // 结果: false,对象类型修改不会影响源对象
console.log(target) // 新对象内没有 say() 函数
</script>
</body>
</html>
2. 自定义深拷贝函数
自定义函数:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>拷贝</title>
</head>
<body>
<script>
function deepClone(source) {
// 防止循环引用
let dependsAddressMap = new Map()
function doDeepClone(source) {
// 浅拷贝
let target = Array.isArray(source) ? [...source] : typeof source === 'object' ? {...source} : source
if (target === source) {
return target
}
// 如果处理过直接返回对象,防止循环引用
if (dependsAddressMap.get(source)) {
return dependsAddressMap.get(source)
}
dependsAddressMap.set(source, target)
// 深拷贝
Reflect.ownKeys(source).forEach(key => {
if (typeof source[key] === 'object') {
target[key] = doDeepClone(source[key])
}
})
return target
}
return doDeepClone(source);
}
</script>
</body>
</html>
使用上边自定义的深拷贝函数,来看看一下效果:
// 源对象
let source = {
name: 'ares5k',
info: {
sex: '男'
},
say(){
console.log('Hello!')
}
}
// 深克隆
let target = deepClone(source)
target.name = '修改性名'
target.info.sex = '修改性别'
// 比较
console.log(source.name === target.name) // 结果: false,基本类型修改不会影响源对象
console.log(source.info.sex === target.info.sex) // 结果: false,对象类型修改不会影响源对象
console.log(source) // 对象结构和值正常
console.log(target) // 对象结构和值正常