浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝
如果能完全明白上面这句话表达的意思,可以直接跳过下面的内容,因为此文主要就是讲上面这句话分开来分析,如果不是很明白,可细细品味,挺好理解。
js数据类型
数据类型 | Value | 存放位置 |
---|---|---|
基本数据类型 | String、Number、Boolean、Undefined、Null、Symbol | 栈内存 |
引用数据类型 | Object、Array、Function | 堆内存 |
栈内存
中。如果进行复制,系统会自动为新的变量在栈内存中分配一个新值。
堆内存
中的,然后在栈内存中保存一个对堆内存中实际对象的引用。所以,JavaScript中对引用数据类型的操作都是操作对象的引用而不是实际的对象。简单来说就是在栈内存中有个名字(地址),通过名字(地址)去堆内存中对应的值。如果进行复制,系统也会自动为新的变量在栈内存中分配一个值,但这个值仅仅是一个地址。也就是说,复制出来的变量和原有的变量具有相同的地址值,指向堆内存中的同一个对象。
为什么基本数据类型存在栈中,而引用数据类型存在堆中呢?
浅拷贝
浅拷贝只是复制栈内存中的引用,而没有复制堆内存中的值。
var arr=[1,2,3,4,5,6]
var arr1=arr;
arr1.push(7)
// console.log(arr) // [1, 2, 3, 4, 5, 6, 7]
// console.log(arr1)// [1, 2, 3, 4, 5, 6, 7]
var obj={
name:'小明',
age:24
}
var obj1=obj;
obj1.sex='男'
console.log(obj === obj1); // true
console.log(obj) //{name: "小明", age: 24, sex: "男"}
console.log(obj1)//{name: "小明", age: 24, sex: "男"}
可以看出,当复制数据发生改变的时候,原有的数据也会跟着改变,这就是浅拷贝。
深拷贝
深拷贝是对目标的完全拷贝,不只是复制了一层引用,值也复制了。
1.利用 JSON 对象中的 parse 和 stringify(拷贝对象里面不能有函数(function))
var obj={
name:'小明',
age:24,
arr:[1,2,3,4,5,6],
child:{
name:'小红',
age:12
},
way:function(){
console.log('拷贝到了吗?')
}
}
var obj1=JSON.parse(JSON.stringify(obj));
obj1.sex='男'
console.log(obj === obj1); // false
console.log(obj) //{name: "小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'小红',age:12}}
console.log(obj1)//{name: "小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'小红',age:12},sex:"男"}
可以看出,当复制的数据改变时,原有的数据不会发生改变,实现了深拷贝,没有问题,但是当对象里面包含函数(function)的时候,会是什么样呢?一起来看一下
var obj={
name:'小明',
age:24,
arr:[1,2,3,4,5,6],
child:{
name:'小红',
age:12
},
way:function(){
console.log('拷贝到了吗?')
}
}
var obj1=JSON.parse(JSON.stringify(obj));
obj1.sex='男'
console.log(obj) //{name: "小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'小红',age:12},way:ƒ()}
console.log(obj1)//{name: "小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'小红',age:12},sex:"男"}
可以看出,当对象里面有函数的时候,是拷贝不到的,因为在JSON.stringify()在转换的过程中undefined、function这些会被忽略掉,所以当对象中存在function的时候,就不能用JSON.parse(JSON.stringify(obj))这个方法来进行深拷贝
。
2. 利用递归来实现重新创建对象进行复制(真正的完全拷贝)
function deepObj(obj) {
let newObj = typeof(obj) === Array ? [] : {}
for (let key in obj) {
if (obj[key] && typeof obj[key] ==='object'){
newObj[key]=deepObj(obj[key])
}else{
newObj[key]=obj[key]
}
}
return newObj
}
var obj = {
name: '小明',
age: 24,
arr: [1, 2, 3, 4, 5, 6],
child: {
name: '小红',
age: 12
},
way: function () {
console.log('拷贝到了吗?')
}
}
let obj1=deepObj(obj)
obj1.name='我不叫小明'
obj1.hobby='打篮球'
obj1.child.name='我也不叫小红'
console.log(obj === obj1); // false
console.log(obj)//{name: "小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'小红',age:12},way:ƒ()}
console.log(obj1)//{name: "我不叫小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'我也不叫小红',age:12},hobby: "打篮球",way:ƒ()}
可以看出,采用递归的方法可以实现真正的完全拷贝,哪怕是对象里面有方法也可以进行拷贝
。
3. slice和concat对数组进行深拷贝(一层数组是深拷贝,二层以后的还是浅拷贝)
//slice方法
//单层数组
var arr=[1,2,3,4,5,6]
var newArr=arr.slice()
newArr.push(7)
console.log(arr==newArr) //false
console.log(arr) //[1, 2, 3, 4, 5, 6]
console.log(newArr) //[1, 2, 3, 4, 5, 6,7]
//多层数组
var arr=[1,[1,2,3],3,4,5,6]
var newArr=arr.slice()
newArr.push(7)
newArr[1][1]=6
console.log(arr==newArr) //false
console.log(arr) //[1, [1,6,3], 3, 4, 5, 6]
console.log(newArr) //[1, [1,6,3], 3, 4, 5, 6,7]
//concat方法:
//单层数组
var arr=[1,2,3,4,5,6]
var newArr=arr.concat()
newArr.push(7)
console.log(arr==newArr) //false
console.log(arr) //[1, 2, 3, 4, 5, 6]
console.log(newArr) //[1, 2, 3, 4, 5, 6,7]
//多层数组
var arr=[1,[1,2,3],3,4,5,6]
var newArr=arr.concat()
newArr.push(7)
newArr[1][1]=6
console.log(arr==newArr) //false
console.log(arr) //[1, [1,6,3], 3, 4, 5, 6]
console.log(newArr) //[1, [1,6,3], 3, 4, 5, 6,7
可以看出,slice方法和concat方法在单层数组进行拷贝的时候没问题,都是深拷贝,但是在第二层的时候却都是浅拷贝,由此说明slcie方法和concat方法只是对数组的第一层进行深拷贝
总结: