前言:在js中我们经常需要操作数据,对于一般的数据类型,我们可以直接使用值赋值的方式,创建一个新的变量,而对于一个引用类型变量,如果我们需要保留一份备份,就需要进行对象拷贝,我们如何进行拷贝呢,可以看看本文
- 是什么?
因为js中的原始类型存在栈中,引用类型存在堆中,再把引用地址存在栈中。所以拷贝一般发生在引用类型上。效果是创建一份新的数据,让新数据拥有和原数据一样的属性值。
- 特点?
拷贝分为
- 浅拷贝:遍历原对象的第一层属性,如果属性是原始值,则直接赋值,如果属性是引用类型值,则赋值属性的引用。
- 深拷贝:遍历对象上的所有属性并赋值给新对象,如果是引用类型,则将递归遍历该属性,直到属性值都为原始值
浅拷贝方法有哪些
...
解构赋值,可以适用于数组、对象
TypeScript
const a = { a: 1, b: { c: 2 } };
const b = { ...a };
// 因为是浅拷贝,所以第一层的属性可以随便修改,不影响原对象
b.a = 2;
// 而引用类型的属性修改会影响原属性
b.b.c = 3;
console.log(a); // { a: 1, b: { c: 3 } }
console.log(b); // { a: 2, b: { c: 3 } }
-
数组方法
slice
浅拷贝数组[].concat[arr]
[...arr]
-
对象方法
-
Object.assign
TypeScriptconst a = { a: 1, b: { c: 2 } }; const b = Object.assign({}, a); // 和 ... 效果一样 // 因为是浅拷贝,所以第一层的属性可以随便修改,不影响原对象 b.a = 2; // 而引用类型的属性修改会影响原属性 b.b.c = 3; console.log(a); // { a: 1, b: { c: 3 } } console.log(b); // { a: 2, b: { c: 3 } }
-
如何实现浅拷贝
递归遍历对象的属性,赋值给新对象
TypeScript
function shallowCopy(obj) {
const res = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
res[key] = obj[key];
}
}
return res;
}
深拷贝方法有哪些
JSON.parse(JSON.stringify)
实现深拷贝
使用JSON序列化的方式存在很多局限性,比如
- 无法处理
undefined
、BigInt
、Symbol
、function() {}
、循环引用
structuredClone
js新规范中的深拷贝方法
局限性
- 无法处理
Symbol
、function
优点可以处理循环引用
TypeScript
const obj = {
a: 1,
b: "1313",
c: false,
d: null,
f: undefined,
g: 123n, // TypeError: Do not know how to serialize a BigInt
// h: Symbol(0),
// j: function () {},
k: {},
l: {},
};
// 创建循环引用
obj.k.a = obj.l;
obj.l.a = obj.k;
// const newObj = JSON.parse(JSON.stringify(obj))
const newObj = structuredClone(obj)
console.log(newObj);
MessageChannel
也可以用来做深拷贝,和structuredClone
一样,也是无法处理Symbol
和function
TypeScript
const obj = {
a: 1,
b: "1313",
c: false,
d: null,
f: undefined,
g: 123n, // TypeError: Do not know how to serialize a BigInt
// h: Symbol(0),
// j: function () {},
k: {},
l: {},
};
// 创建循环引用
obj.k.a = obj.l;
obj.l.a = obj.k;
function messageChannelClone(obj) {
return new Promise((resolve, reject) => {
const { port1, port2 } = new MessageChannel();
port1.postMessage(obj);
port2.onmessage = (msg) => {
resolve(msg.data);
port1.close()
};
});
}
const newObj = await messageChannelClone(obj);
console.log(newObj);
如何实现深拷贝
简单版本的深拷贝,可以处理原始类型和引用类型中的数组对象、以及循环引用
TypeScript
function deepClone(obj, hash = new WeakMap()) {
// 处理null和非对象 原始值
if (obj === null || typeof obj !== "object") {
return obj;
}
// 剩余的均为引用类型
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
let res = Array.isArray(obj) ? [] : {};
// 将所有对象的引用都记录到 WeakMap 中,以便后续如果有重复的引用,可以直接拿出赋值
hash.set(obj, res);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
// 如果是数组,则key就是数组索引
res[key] = deepClone(obj[key], hash);
}
}
return res;
}