一、深拷贝 浅拷贝
基本数据类型的特点:直接存储在栈(stack)中的数据
引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里
1. 为什么要用深拷贝
在希望改变**新的数组(对象)**的前提下,而不去改变**原始数组(对象)**
2. 深拷贝
深拷贝会创建一个一模一样的对象,新旧对象**分处于不同的内存空间**,即不会共享内存。
- 拷贝第一层级的对象属性或者数组元素。
- 递归拷贝所有的对象属性和数组元素。
- 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
1)深拷贝方法
1. JSON 序列化JSON.parse(JSON.stringify(Obj))
- JSON序列化 `无法拷贝函数`
- JSON序列化 `会忽略属性值为undefined`
实例:
<script lang="ts" setup>
import { ref } from "vue";
// JSON序列化深拷贝
interface refobj1 {
data: Array<number>;
meta: object;
undefinedData: undefined;
}
let RefObj = ref<refobj1>({
data: [1, 2],
meta: {
data: 1,
},
undefinedData: undefined,
});
// 获取变量refObj的值
let refObjValue: object = RefObj.value;
console.log("原始对象", refObjValue);
// JSON 序列化
let JSONline = JSON.parse(JSON.stringify(RefObj.value));
console.log("JSON序列化浅拷贝", JSONline);
</script>
结果:
ref 定义的对象 是由 Proxy 代理的,所有的值须在 `Target` 中查看
由图知,undefined 属性 在深拷贝时 丢失了
2. for……in 手写深拷贝函数 (若需要同时拷贝函数,值为undefined的属性)
// 手写for循环实现深拷贝
interface refobj2 {
data: Array<number>;
meta: object;
undefinedData: undefined;
nullValue: null;
}
let RefObj2 = ref<refobj2>({
data: [1, 2],
meta: {
data: "2",
},
undefinedData: undefined,
nullValue: null,
});
// 重写typeof方法
function typeOf(obj: any): any {
const toString: any = Object.prototype.toString;
const map: any = {
"[object Boolean]": "boolean",
"[object Number]": "number",
"[object String]": "string",
"[object Function]": "function",
"[object Array]": "array",
"[object Date]": "date",
"[object RegExp]": "regExp",
"[object Undefined]": "undefined",
"[object Null]": "null",
"[object Object]": "object",
};
return map[toString.call(obj)];
}
// 定义一个深拷贝函数
function deepClone(data: any): any {
// 获取传入拷贝函数的数据类型
const type = typeOf(data);
// 定义一个返回any类型的数据
let reData: any;
// 递归遍历一个array类型数据,
if (type === "array") {
reData = [];
for (let i = 0; i < data.length; i++) {
reData.push(deepClone(data[i]));
}
} else if (type === "object") {
//递归遍历一个object类型数据
reData = {};
for (const i in data) {
reData[i] = deepClone(data[i]);
}
} else {
// 返回基本数据类型
return data;
}
// 将any类型的数据return出去,作为deepClone的结果
return reData;
}
let obj2: any = deepClone(RefObj2.value);
// 为true, 表示两个数据是相同(使用同一块内存地址);
// 为false,则表示深拷贝成功(使用不同块的内存地址)
console.log(obj2 === RefObj2.value);
console.log("原始对象", RefObj2.value);
console.log("新对象", obj2);
结果:
3. 浅拷贝
浅拷贝只复制某个对象的指针,而不是对象本身,新旧对象还是**共享一个内存块**。
- 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
- 如果拷贝对象的属性是基本类型,那么拷贝的就是属性的值;如果对象属性是一个引用类型(内存地址),那么拷贝的就是内存地址,因此,如果其中一个对象改变这个地址,就会影响到另一个对象。
4. 赋值和浅拷贝的区别
当把一个对象的值赋值给一个新的变量时,赋的其实是该对象在堆栈中的地址,而不是堆栈中的数据,也就是说,两个对象指向的是同一个存储空间,无论改变那个对象的内容,其实都是改变这个存储空间的内容,因此,两个对象的内容是联动的。 浅拷贝是按位拷贝对象,它会创建一个一个新的对象,这个对象有着原始对象属性值的一份精确拷贝。默认拷贝构造函数只是对对象成员逐个依次拷贝,即只复制对象空间而不是复制资源。
1)浅拷贝和赋值实例
interface shallowcopy {
name: string;
age: number;
langguage: Array<Array<number> | number>;
obj: {
name: string;
age: number;
};
}
interface objassignment {
name: string;
age: number;
langguage: Array<Array<number> | number>;
}
let ObjAssignment = ref<objassignment>({
name: "小米",
age: 18,
langguage: [[1, 2], 3, [5, 6]],
});
let ShallowCopy = ref<shallowcopy>({
name: "小红",
age: 26,
langguage: [[1, 2], 3, [5, 6]],
obj: {
name: "3333",
age: 444,
},
});
// 对象赋值
var obj1: any = ObjAssignment.value;
console.log("原始对象:", ObjAssignment.value);
console.log("对象赋值→新对象:", obj1);
// 修改 新对象
obj1.name = "对象赋值"
obj1.langguage[0] = [1,2,3,4,5]
// 浅拷贝
function shallowCopy(obj: any) {
var result: any = {};
var key: string;
for (key in obj) {
if (obj[key]) {
result[key] = obj[key];
}
}
return result;
}
let NewShallowCopy = shallowCopy(ShallowCopy.value);
console.log("原对象:", ShallowCopy.value);
console.log("浅拷贝新对象:", NewShallowCopy);
// 修改新对象的值
NewShallowCopy.obj.name = "浅拷贝"; // 修改浅拷贝结果的属性值
NewShallowCopy.name = "浅拷贝"; // 修改浅拷贝结果对象值
NewShallowCopy.langguage[0] = [9999,99999] // 修改浅拷贝结果的数组值
console.log("浅拷贝修改后的原对象:", ShallowCopy.value);
console.log("浅拷贝修改后的新对象:", NewShallowCopy);
1.对象赋值结果
2.浅拷贝结果
通过浅拷贝出来的对象里面,如果是基本属性类型的,并没有继续通过proxy代理,而是直接将属性值提取了出来;
如果是一个引用类型(对象、数组等)则直接拿的原来的内存地址,所以还是通过proxy代理的数据。
后面去修改通过浅拷贝的数据时:
基本属性类型的数据并没有发生改变
而引用类型数据内的内容影响到了原数据内容