持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
前言
首先深拷贝与浅拷贝只是针对引用数据类型而言,如果是基础数据类型则没有这个说法。因为基础数据类型就是简单的赋值。
深拷贝与浅拷贝的区别
对于js里创建的引用类型对象,都会在栈中存放一个该对象的访问地址,然后在堆内存开辟一块空间存放具体的值,这个地址就指向这个值。
浅拷贝
对于引用类型数据而言,浅拷贝拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。因为新旧对象这时候都用的同一个内存地址,如果改变了其中一个对象的值,即改变了堆内存里的值,那么另一个也会改变。
深拷贝
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象。这时候2个对象的内存地址是不一样的,即堆内存开辟的空间也不一样。所以改变其中一个对象的值,跟另一个对象都没有关系。
实现浅拷贝的方式
直接赋值
let obj1 = {
name:'jy',
age:22,
action:{
name:'swim'
}
}
let obj2=obj1
obj2.name='gogo'
obj2.action.name='haha'
console.log('obj1: ', obj1);
console.log('obj2: ', obj2);
console打印结果如下
obj1: { name: 'gogo', age: 22, action: { name: 'haha' } }
obj2: { name: 'gogo', age: 22, action: { name: 'haha' } }
...展开运算符
这里需要注意的是,如果这个对象只有一层,那么是深拷贝,如果有多层,那么就是浅拷贝
let obj1 = {
name:'jy',
age:22,
action:{
name:'swim'
}
}
let obj2 = {...obj1}
obj1.name='gogo'
obj1.action.name='haha'
console.log('obj1: ', obj1);
console.log('obj2: ', obj2);
Object.assign()
这个方法同展开运算符一样
let obj1 = {
name:'jy',
age:22,
action:{
name:'swim'
}
}
let obj2=Object.assign({},obj1)
obj2.name='gogo'
obj2.action.name='haha'
console.log('obj1: ', obj1);
console.log('obj2: ', obj2);
Array.prototype.concat()与 Array.prototype.slice()
这2个方法都是如果数组只有一层,那么是深拷贝,如果多层,那么就是浅拷贝
let arr1:any[] = [
{
name: 'gogo'
},
'aaa',
'bbb',
];
let arr2 = arr1.concat([]);
// let arr2 = arr1.slice()([]);
arr2[0].name = 'concat';
arr2[1] = 'ccc';
console.log('arr1===>', arr1);
console.log('arr2===>', arr2);
实现深拷贝的方式
JSON.parse(JSON.stringify())
let obj1 = {
name:'jy',
age:22,
action:{
name:'swim'
}
}
let obj2=JSON.parse(JSON.stringify(obj1))
obj2.name='JSON.parse'
obj2.action.name='JSON.stringify'
console.log('obj1: ', obj1);
console.log('obj2: ', obj2);
自己实现一个深拷贝
//用于添加字段接口
interface LooseObject {
[key: string]: any
}
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
* @param {Map} map 用于存储循环引用对象的地址
*/
function deepClone(obj = {}, map = new Map()) {
if (typeof obj !== "object") {
return obj;
}
if (map.get(obj)) {
return map.get(obj);
}
let result:LooseObject = {};
// 初始化返回结果
if (
obj instanceof Array ||
// 加 || 的原因是为了防止 Array 的 prototype 被重写,Array.isArray 也是如此
Object.prototype.toString.call(obj) === "[object Array]"
) {
result = [];
}
// 防止循环引用
map.set(obj, result);
Object.entries(obj)
.forEach(key => {
if (typeof key[1] == 'object') {
// 递归调用
result[key[0]] = deepClone(key[1] as object, map);
}else{
result[key[0]]=key[1]
}
})
// 返回结果
return result;
}
export default {deepClone}
然后在另一个文件里使用这个方法
import deep from './cloneDeep'
let map=new Map()
let result = deep.deepClone(people,map)
result.name = 'clone'
result.food.weight = 600
console.log('people: ', people);
console.log('result: ', result);
可以看到2个对象完全不影响,因此实现了深拷贝。
总结
这是对深拷贝与浅拷贝的一点总结,在开发中要注意数据的改变应该用深拷贝还是浅拷贝,多多练习才会运用自如。