带你详细了解JS中的浅拷贝与深拷贝
前言
首先我们从字面意思来理解拷贝即复制(copy)的意思,在JS中它分为浅拷贝(shallow copy)
和深拷贝(deep copy)
两种,在我们实际的项目开发过程中有时需要我们去实现拷贝,同时面试时可能也会问相关的问题。
数据类型
深入了解JS中的浅拷贝与深拷贝之前,我们来看一下JS中的数据类型。JS中数据类型分为两种:一种是基本数据类型(Number,String,Boolean,Null,Undefined,Symbol(es6新增))
,这种数据类型存储在栈中;一种是引用类型(Array,Object,Function)
,这种数据类型则是地址存储在栈中,数据存储在堆内存中。看下图:
浅拷贝(shallow copy)
什么是浅拷贝?
在栈中开辟一个新的空间,存放拷贝的一份数据,基本数据类型的值会全部拷贝一遍,而引用类型则只是复制地址存放在新开辟的栈空间中,当原引用类型的数据改变时拷贝得到的对象也会改变,简单来说浅拷贝是拷贝得到的对象会受原对象的影响
。
实现浅拷贝的方法
1.引用类型的直接赋值
let a = { name: 'song' }
let b = a
a.name = 'jian'
console.log(b);
此时结果为{name = 'jian'}
,通过改变a对象,b对象也改变了,像这样也可以称之为浅拷贝。
2.Object.create()
let a = { name: song }
let b = Object.create(a)
3.arr.concat() 数组拼接
let arr = [1, '1', true, null, undefined, { n: 'as' }]
let newArr = arr.concat()
arr[0] = 2
arr[5].n = 'qw'
console.log(newArr);
结果为 [1, '1', true, null, undefined, { n: 'qw' }]
,我们可以看到此时基本数据类型拷贝的对象没有受原对象的影响,但是引用类型的拷贝还是受了影响;也就是说,基本数据类型深拷贝了,引用数据类型只是浅拷贝。像这样只要拷贝得不彻底,我们都称为浅拷贝。
4.arr.slice() 数组删除
let arr = [1, '1', true, null, undefined, { n: 'as' }]
let newArr = arr.slice()//从0砍到最后一位,返回出一个新的数组
arr[0] = 2
arr[5].n = 'qw'
console.log(newArr);
这个结果和arr.concat()
方法一样,也是属于浅拷贝。
5.object.assign() 对象合并
let obj1 = {
a: 1,
b: {
c: 2
},
sym: Symbol(1)
}
let obj2 = {}
Object.assign(obj2, obj1)
obj1.a = 3
obj1.b.c = 4
console.log(obj2);
代码运行结果为:
我们可以看到也是上面两种方法的同样问题,拷贝的引用类型依旧存在着访问共同堆的问题,也只是浅拷贝。
扩展运算符方式
语法为let obj2 = {...obj1}
此方法结果和object.assign()
一样,都是属于浅拷贝。
手写浅拷贝函数
原理
用typeof方法判断实参是否为对象,创建一个空对象(
数组也是对象
),遍历原对象,把原对象里面的值一一赋值给空对象。(只考虑对象)
function shallowCopy(obj) {
//只考虑对象,不是对象或为空就返回出去
if (typeof obj !== 'object' || obj === null)
return
//创建一个空对象
let newObj = obj instanceof Array ? [] : {}
//遍历原对象,将原对象的值赋予空对象
for (let key in obj) {
newObj = obj[key]
}
//返回出拷贝好的对象
return newObj
}
深拷贝
什么是深拷贝?
与浅拷贝相对应,它不仅会在栈中开辟一个空间存放数据,如果被拷贝对象有引用类型时,它也会在堆中开辟另一个空间来存放引用类型的数据,这样两个对象指向的是不同的地址;深拷贝得到的对象就不会受原对象的影响
。
用JSON.parse(JSON.stringify(obj))进行深拷贝
- JSON.stringfy()将对象序列化成json对象(转换成JSON字符串)
- JSON.parse()反序列化———将json对象反序列化成js对象(解析成对象)
let obj = { name: '高富帅', age: 20 }
let newObj = JSON.parse(JSON.stringify(obj))
let arr = [1, '1', null, true, undefined]
let newArr = JSON.parse(JSON.stringify(arr))
arr[1] = 'a'
obj.name = 'song'
console.log(newObj);
console.log(newArr);
结果为:
从结果我们可以看出通过
JSON.parse(JSON.stringify(obj))
方法进行的拷贝原对象的改变无法影响拷贝出的对象,这就是深拷贝。
注意:此方法不能拷贝undefined,函数,无法处理循环引用的情况,不能拷贝Symbol类型。
手写深拷贝函数(面试考点)
原理
用typeof方法判断实参是否为对象,创建一个空对象(
数组也是对象
),用for key in obj
遍历原对象,obj[key]
是基本类型才直接赋值,如果是引用类型,就需要循环递归一层一层找到基本类型赋值。
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) return
let newObj = obj instanceof Array ? [] : {}
//循环遍历对象
for (let key in obj) {
// obj[key] 是原始类型才直接赋值,是引用类型就循环递归
if (typeof obj[key] === 'object' && obj[key] !== null) {
// 递归实现多层引用类型的赋值
newObj[key] = deepCopy(obj[key])
} else {
//直接赋值
newObj[key] = obj[key]
}
}
return newObj
}
小结
我们在开发项目的过程中,常常需要我们去分清楚浅拷贝与深拷贝。 如果你和同事一起开发一个项目,你们都需要用到一个公共的对象,方法之一就是深拷贝一个对象。最后感谢各位的观看,码字不易,点个免费的赞吧~
转载自:https://juejin.cn/post/7224043114360406071