浅拷贝与深拷贝
1. 浅拷贝与深拷贝
1.1 浅拷贝
- 浅拷贝中,简单数据类型是值传递。
- 引用数据类型,传递的是地址,修改拷贝对象中的对象元素属性,原对象中对象属性也会被修改。
1.2 深拷贝
深拷贝传递的是对象,并在堆中为其分配一个新的内存空间,修改拷贝对象的属性不会影响到原对象。
2. 拷贝方法
2.1 浅拷贝方法
2.1.1 拷贝对象
- 方法:
// 1. 对象扩展运算符{...obj}
{...sources}
// 2.Object.assign()
Object.assign(target, sources)
- 例:
<script>
let obj = {
uname: '美杜莎',
age: 18,
sex: '女',
body: {
uname: '七彩吞天蟒',
age: 17
}
}
console.log('obj=', obj)
// 1. 使用对象扩展运算符进行浅拷贝 {...对象名}
let p1 = { ...obj }
console.log('p1=', p1) // {uname:'美杜莎',age: 18,sex: '女',body:{...}}
// 2. 使用对象方法 assign() 进行浅拷贝
let p2 = Object.assign({}, obj)
console.log('p2=', p2) // {uname:'美杜莎',age: 18,sex: '女',body:{...}}
// 3. 浅拷贝中:引用数据类型,传递的是地址,修改拷贝对象中的对象元素属性,原对象中对象属性也会被修改。
let p3 = { ...obj }
p3.body.age = 12
console.log('p3=', p3)
console.log('obj=', obj)
</script>
由图可见,修改拷贝对象中的嵌套对象元素值,原对象中的值也被改变了。
2.1.2 拷贝数组
使用以下数组方法返回的都是一个新的数组,不会改变原来的数组。
- 方法:
// 1. Array.prototype.slice() 截取
arr.slice()
// 2. Array.prototype.concat() 结合
arr.concat()
// 3. [...arr]
[...arr]
- 例:
<script>
let arr = [1, 2, 3, 4, 5, 6, 7]
console.log('arr=', arr) // arr= (7) [1, 2, 3, 4, 5, 6, 7]
// 1.使用Array.slice() 截取方法
// slice(start) start代表索引号,当只有一个参数时,表示从这个索引号开始,一直截取到最后
let arr1 = arr.slice()
let arr2 = arr.slice(3)
console.log('arr1=', arr1) // arr1= (7) [1, 2, 3, 4, 5, 6, 7]
console.log('arr2=', arr2) // arr2= (4) [4, 5, 6, 7]
// slice(start,end) 从start位置开始,截取到end位置,end位置上的值保留
let arr3 = arr.slice(3, 6)
let arr4 = arr.slice(9, 3) // start不可大于end,否则截取不成功
console.log('arr3=', arr3) // arr3= (3) [4, 5, 6]
console.log('arr4=', arr4) // arr4= []
// 2. 使用Array.concat() 合并方法
let arr5 = arr.concat()
console.log('arr5=', arr5) // arr5= (7) [1, 2, 3, 4, 5, 6, 7]
// 3. 使用扩展运算符
let arr6 = [...arr]
console.log('arr6=', arr6) // arr6= (7) [1, 2, 3, 4, 5, 6, 7]
</script>
2.2 深拷贝方法
- 深拷贝的好处就是新对象和旧对象互不影响。
- 实现深拷贝的方法有如下:
2.2.1 递归实现
- 简单数据直接使用遍历进行值传递
- 需要判断复杂数据类型为数组还是对象(一定要先处理数组中的数据,再处理对象中的数据)
- 复杂数据类型则需要再调用递归函数进行再遍历,直到其中的数据为简单类型为止。
<!--
一、具体实现思路:
1.若旧对象中属性为简单数据类型,直接遍历进行值传递
1.若旧对象中属性为数组类型,需要为新数组初始化一个空的相同属性的数组,再调用递归函数进行判断,直到最深一层为简单数据类型,直接进行值传递;
2.若旧对象中属性为对象类型,需要为新数组初始化一个空的相同属性的对象,再调用递归函数进行判断,直到最深一层为简单数据类型,直接进行值传递;
二、注意事项:
1.先判断数组,再判断对象 因为:console.log(Array instanceof Object) 结果为 true
2.当遍历对象为数组类型,key为索引号;若为对象,key为属性名
3.key为变量,所以需要使用[]动态为新数组添加属性
-->
<script>
const obj = {
uname: '美杜莎',
age: 18,
state: ['七彩吞天蟒', '美杜莎'],
vertion1: {
uname: '七彩吞天蟒',
level: '魔兽之尊'
}
}
console.log('obj=', obj)
// 定义拷贝函数 反复对复杂数据类型的再遍历 直到最深一层为简单数据类型
function Copy(targetObj, sourcesObj) {
for (let key in sourcesObj) {
// 1. 处理数组数据
if (sourcesObj[key] instanceof Array) {
// 初始化 targetObj[key]也为数组
targetObj[key] = []
// 调用递归函数, 遍历直到为简单数据为止
Copy(targetObj[key], sourcesObj[key])
}
// 2. 处理对象数据
else if (sourcesObj[key] instanceof Object) {
// 初始化 targetObj[key]也为数组
targetObj[key] = {}
// 调用递归函数, 遍历直到为简单数据为止
Copy(targetObj[key], sourcesObj[key])
}
else {
// 简单数据类型值传递
// targetObj[uname]=sourcesObj[uname]
targetObj[key] = sourcesObj[key]
}
}
}
const p1 = {}
// 调用拷贝函数
Copy(p1, obj)
// console.log('p1=', p1) 和obj一样
p1.age = 20
p1.state[0] = '小七'
p1.vertion1.level = '斗圣'
console.log('p1=', p1)
</script>
2.2.2 lodash 实现
- Lodash 是一个一致性、模块化、高性能的 JS的一个库。利用里面的cloneDeep可实现深拷贝。
- 使用:下包 -> 导包 -> 语法
- 语法:
targetObj=_.cloneDeep(sourcesObj)
2.2.3 JSON实现
<script>
const obj = {
uname: '美杜莎',
age: 18,
state: ['七彩吞天蟒', '美杜莎'],
vertion1: {
uname: '七彩吞天蟒',
level: '魔兽之尊'
}
}
console.log('obj=', obj)
/*
// 将对象转换成字符串,以值的方式存储在栈空间中
let p1 = JSON.stringify(obj)
// 将字符串转换成一个新的对象,重新开辟空间存储,与原对象互不影响
let p2 = JSON.parse(p1)
console.log('p1=',p1)
console.log('p2=',p2) */
const p1 = JSON.parse(JSON.stringify(obj))
p1.vertion1.level = '斗圣'
console.log('p1=', p1)
</script>
3. 总结
- 浅拷贝传递的是地址,原对象与新对象之间会相互影响;深拷贝传递的是对象,不会相互影响。
- 浅拷贝方法有:对象扩展运算符、Object.assign()、Array.slice() 截取、Array.concat() 合并;深拷贝方法有:递归、Lodash中的cloneDeep方法和JSON转化。