高性能实现Javascript深度拷贝
2023-09-17 04:12:46
高性能版深度拷贝
在JavaScript中,由于引用类型的存在,使得我们在使用的过程中,很容易发生一些可预知的副作用,比如下面的例子:
const a = {
name: 'John',
age: 30
};
const b = a;
b.name = 'Mary';
console.log(a.name); // Mary
显然,上面的例子中a的属性name也被修改了。对于上面的数据结构来说,也许我们浅拷贝就足够了,但是如果数据结构变得更加复杂,比如下面的例子:
const a = {
name: 'John',
age: 30,
address: {
street: '123 Main Street',
city: 'Anytown',
state: 'CA',
zip: '12345'
}
};
const b = a;
b.address.street = '456 Elm Street';
console.log(a.address.street); // 456 Elm Street
在这个例子中,b对a的地址对象的修改也影响了a的地址对象。这是因为JavaScript中的对象是引用类型,这意味着它们在内存中存储为指向另一个内存位置的指针。当我们对一个对象进行修改时,我们实际上只是修改了这个指针所指向的内存位置。
为了避免这种副作用,我们可以使用深度拷贝来创建一个对象的副本,这个副本与原对象完全独立,对副本所做的任何修改都不会影响原对象。
在JavaScript中,有多种方法可以实现深度拷贝。一种简单的方法是使用JSON.stringify()和JSON.parse()方法。这两个方法可以将一个对象转换成一个JSON字符串,然后再将JSON字符串转换成一个对象。这种方法很简单,但是它也有一个缺点,就是它不能拷贝函数和Symbol值。
另一种实现深度拷贝的方法是使用递归函数。这种方法可以拷贝任何类型的对象,包括函数和Symbol值。但是,这种方法的缺点是它可能非常慢,尤其是当对象非常大的时候。
为了解决上述两种方法的缺点,我们可以使用一种叫做“标记-清除”的算法来实现深度拷贝。这种算法首先给对象打上标记,然后遍历对象,复制所有标记过的对象。这种方法既简单又高效,而且它可以拷贝任何类型的对象。
以下是使用“标记-清除”算法实现的深度拷贝函数:
function deepCopy(obj) {
const seen = new WeakMap();
function copy(obj) {
if (typeof obj === 'object' && obj !== null) {
if (seen.has(obj)) {
return seen.get(obj);
}
seen.set(obj, obj);
if (Array.isArray(obj)) {
return obj.map(copy);
}
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
const newObj = {};
for (const key in obj) {
newObj[key] = copy(obj[key]);
}
return newObj;
}
return obj;
}
return copy(obj);
}
这个函数可以拷贝任何类型的对象,包括函数、Symbol值和循环引用。而且,它的性能也非常高,即使对于非常大的对象也能在短时间内完成拷贝。
深度拷贝在JavaScript中非常有用,它可以帮助我们避免引用类型的副作用。我们可以使用JSON.stringify()和JSON.parse()方法、递归函数或“标记-清除”算法来实现深度拷贝。