JSON.parse(JSON.stringify(object))实现深拷贝引发的问题

418 阅读3分钟

该点内容的灵感来源于刷到一篇文章来分享自己项目中前端接口防止重复调用的解决方式,博主抛出了自己的部分代码,细心的网友发现了他代码里的问题,并展开了一些列的讨论,觉得挺有意思就也了解了一下这块内容。

JSON.parse(JSON.stringify(object)) 是一种常用的 JavaScript 技巧,用于实现对象的深拷贝。它通过将对象转换为 JSON 字符串,然后再解析回对象,从而创建一个新的对象副本。

代码示例:

// 原始对象
const originalObject = {
    name: 'Alice',
    age: 30,
    hobbies: ['reading', 'traveling'],
    address: {
        city: 'New York',
        zip: '10001'
    }
};

// 深拷贝
const copiedObject = JSON.parse(JSON.stringify(originalObject));

// 修改拷贝对象
copiedObject.name = 'Bob';
copiedObject.hobbies.push('cooking');
copiedObject.address.city = 'Los Angeles';

console.log('Original Object:', originalObject);
console.log('Copied Object:', copiedObject);

运行结果

image.png

但是这种深拷贝也会引发出来一些问题:

  1. 经过 JSON.stringifyJSON.parse 处理后,新对象会丢失原对象的原型链,也就是说新对象不再是一个实例,无法访问原型上的方法和属性。
function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
};

// 创建一个实例
const originalPerson = new Person('Alice');
originalPerson.sayHello()
// 深拷贝
const copiedPerson = JSON.parse(JSON.stringify(originalPerson));
// 测试原型链
console.log(copiedPerson instanceof Person); 
copiedPerson.sayHello()

运行结果

image.png 2. JSON.stringify 无法序列化函数、undefined、symbol 和循环引用的对象。尝试这样做会导致这些数据丢失或抛出错误。

const obj = {
    name: 'Alice',
    age: 30,
    greet: function() { // 函数
        console.log('Hello!');
    },
    address: {
        city: 'New York',
        zip: undefined, // undefined
    },
    uniqueId: Symbol('id'), // Symbol
};
try {
    // 尝试序列化对象
    const jsonString = JSON.parse(JSON.stringify(obj));
    console.log('JSON String:', jsonString);
} catch (error) {
    console.error('Error during JSON.stringify:', error.message);
}

运行结果

image.png 3. 日期对象在被序列化和解析后会变成字符串,而不是日期对象。

// 创建一个包含日期对象的对象
const originalObject = {
    birthDate: new Date('1990-01-01'), // 日期对象
};

// 深拷贝
const copiedObject = JSON.parse(JSON.stringify(originalObject));

// 输出原对象和拷贝对象
console.log('Original Object:', originalObject);
console.log('Copied Object:', copiedObject);

// 检查 copiedObject 中的 birthDate 类型
console.log('Type of birthDate in copiedObject:', typeof copiedObject.birthDate);
console.log('Is birthDate an instance of Date?', copiedObject.birthDate instanceof Date); // false

运行结果

image.png

  1. 对象的属性顺序可能会在序列化和解析过程中改变。
// 创建一个对象,属性顺序为特定顺序
const originalObject = {
    1: 's',
    3: 'w',
    2: 'q',
};
// 深拷贝
const copiedObject = JSON.parse(JSON.stringify(originalObject));
// 输出原对象和拷贝对象
console.log('Copied Object:', copiedObject);
// 检查属性顺序
console.log('Property Order in Copied Object:', Object.keys(copiedObject));

运行结果

image.png 5. JSON.stringifyJSON.parse` 都有性能开销,尤其是对于大型对象或数组,这种转换可能会很慢。

  1. 如果对象中有不可序列化的属性(例如,带有循环引用的属性),这些属性会被丢失。
// 创建一个包含循环引用的对象
const originalObject = {
    name: 'Alice',
    age: 30,
};

// 添加循环引用
originalObject.self = originalObject;

try {
    // 尝试序列化对象
    const jsonString = JSON.stringify(originalObject);
    console.log('JSON String:', jsonString);
} catch (error) {
    console.error('Error during JSON.stringify:', error.message);
}

// 尝试进行深拷贝
try {
    const copiedObject = JSON.parse(JSON.stringify(originalObject));
    console.log('Copied Object:', copiedObject);
} catch (error) {
    console.error('Error during JSON.parse:', error.message);
}

运行结果

image.png 说明

  1. 循环引用: 在 originalObject 中,self 属性引用了自身,形成了循环引用。

  2. 序列化过程: 当尝试使用 JSON.stringify 时,JavaScript 会抛出错误,提示“Converting circular structure to JSON”,因为 JSON 不支持循环引用。

  3. 深拷贝失败: 由于序列化失败,后续的 JSON.parse 操作不会执行,因此不会生成新的对象副本。