Problems in implementing deep copy using JSON method

In today’s front-end interviews, deep copies appear very frequently. Among the general questions, you may first be asked, what is deep copy, and what are the ways to implement deep copy? You may answer a few points, such as through JSON.strinfy and JSON.parse provided by the JSON object, because this This implementation method is extremely simple, just one line of code, I feel happy, I don’t panic at all if you ask me to write it by hand. So, if the interviewer asks backhand, are there any problems in implementing deep copy through the method provided by JSON? Can you give a satisfactory answer?

What are deep copy and shallow copy

For students who don’t understand deep copy, we first introduce the concepts of deep copy and shallow copy in JavaScript, and then discuss the implementation method and the problems therein.

Public account: Code program life, personal website: https://creatorblog.cn

In JavaScript, we often need to copy objects or arrays in order to operate without affecting the original data. At this time, we need to distinguish between the concepts of deep copy and shallow copy.

  • Shallow copy: Only the first-level attributes of an object or array are copied. If the value of the attribute is a reference type, then the reference address is copied, not the real value. In this way, modifying the copied object or array may affect the original object or array.
  • Deep copy: Completely copy all hierarchical attributes of an object or array. If the value of the attribute is a reference type, then copy its internal attributes recursively until all values are of basic types. In this way, modifying the copied object or array will not affect the original object or array.

For example, suppose we have an object obj with the following structure:

let obj = {<!-- -->
  name: "Tom",
  age: 18,
  hobbies: ["basketball", "football"],
  friend: {<!-- -->
    name: "Jerry",
    age: 17
  }
};

If we use a shallow copy method, such as Object.assign or the spread operator, to copy obj and get a new object clone, then The structure of clone is as follows:

let clone = Object.assign({<!-- -->}, obj); // or let clone = {...obj};

The name and age properties of clone are basic types, so the real values are copied, while the hobbies and friend attribute is a reference type, so what is copied is the reference address, pointing to the attributes of the original object. In this way, if we modify the hobbies or friend attributes of clone, it will affect the corresponding attributes of obj, such as :

clone.hobbies.push("tennis"); // Modify clone's hobbies attribute
console.log(obj.hobbies); // ["basketball", "football", "tennis"], obj's hobbies attribute has also been modified

If we use the deep copy method, such as JSON.parse(JSON.stringify(obj)), to copy obj and get a new object clone code>, then the structure of clone is as follows:

let clone = JSON.parse(JSON.stringify(obj));

All properties of clone are basic types, or newly created reference types, and have no relationship with the original object. In this way, if we modify any attributes of clone, it will not affect the corresponding attributes of obj, for example:

clone.friend.name = "Bob"; // Modify the friend attribute of clone
console.log(obj.friend.name); // "Jerry", obj's friend attribute has not been modified

Why use JSON method to implement deep copy

Using JSON.parse(JSON.stringify(obj)) to implement deep copy is a very simple and effective method.

Its principle is to use JSON.stringify to serialize an object or array into a JSON string, and then use JSON.parse to parse the string For a new object or array, thus implementing deep copy.

The advantages of this approach are:

  • The code is concise and can be done in one line
  • There is no need to consider the hierarchical structure of objects or arrays, and nested situations can be automatically handled
  • No need to consider the property names of objects or arrays, all properties can be copied automatically

Problems in implementing deep copy using JSON method

Although it is convenient to use JSON.parse(JSON.stringify(obj)) to implement deep copy, it also has many limitations and problems that we need to pay attention to. These issues mainly include:

  • Cannot handle circular references: If there is a circular reference in an object or array, that is, the value of an attribute is the object or array itself, or an ancestor attribute of the object or array, then JSON.stringify will report an error and cannot be serialized. for example:
let obj = {<!-- -->
  name: "Tom",
  age: 18,
  hobbies: ["basketball", "football"],
  friend: {<!-- -->
    name: "Jerry",
    age: 17
  }
};
obj.self = obj; // The self attribute of obj points to obj itself, forming a circular reference
let clone = JSON.parse(JSON.stringify(obj)); // TypeError: Converting circular structure to JSON
  • Cannot handle undefined, Symbol and other types of values: If there are undefined, Symbol and other types of values in the object or array, then JSON .stringify will lose these values and cannot be serialized. for example:
let obj = {<!-- -->
  name: "Tom",
  age: undefined, // The age property of obj is undefined
  hobbies: ["basketball", "football"],
  friend: {<!-- -->
    name: "Jerry",
    age: 17
  },
  [Symbol("id")]: 123 // Symbol attribute of obj
};
let clone = JSON.parse(JSON.stringify(obj));
console.log(clone); // {name: "Tom", hobbies: ["basketball", "football"], friend: {name: "Jerry", age: 17}} , the age attribute and Symbol attribute of clone are lost
  • Cannot process values of types such as Date and regular expressions: If there are values of types such as Date and regular expressions in the object or array, then JSON.parse( JSON.stringify(obj)) will be distorted and cannot be restored to the original type. for example:
let obj = {<!-- -->
  name: "Tom",
  age: 18,
  hobbies: ["basketball", "football"],
  friend: {<!-- -->
    name: "Jerry",
    age: 17
  },
  birthday: new Date("2000-01-01"), // obj's birthday attribute is of Date type
  pattern: /\w + / // The pattern attribute of obj is a regular expression type
};
let clone = JSON.parse(JSON.stringify(obj));
console.log(clone.birthday); // "2000-01-01T00:00:00.000Z", clone's birthday attribute becomes a string
console.log(clone.pattern); // {}, the pattern attribute of clone becomes an empty object
  • Cannot handle objects generated by constructors: If the object is generated by a constructor, then JSON.parse(JSON.stringify(obj)) will discard the object’s constructor, cannot be restored to the original type. for example:
function Person(name, age) {<!-- -->
  this.name = name;
  this.age = age;
}
let obj = new Person("Tom", 18); // obj is generated by the Person constructor
let clone = JSON.parse(JSON.stringify(obj));
console.log(clone.constructor); // [Function: Object], clone's constructor becomes Object
console.log(clone instanceof Person); // false, clone is not an instance of Person

How to solve the problems of deep copying in JSON method

To solve the problems of deep copying using the JSON method, we can adopt the following solutions:

  • Use recursive method: Use recursion to traverse each attribute of the object or array, determine the type of the attribute, if it is a basic type, copy it directly, if it is a reference type, create a new object or array, and continue the recursion Copy, this method can handle circular reference situations, but you need to pay attention to the risk of stack overflow.
  • Use third-party library method: Use some mature third-party libraries, such as lodash, jQuery, etc., which provide some deep copy functions. Can handle various types of values, but there are some performance or compatibility issues.
  • Use special processing methods: For some special types, such as Date, regular expressions, constructors, etc., we can use some special processing methods to ensure deep copying Correctness. For example, for the Date type, we can use new Date(obj.getTime()) to copy a new Date object. For regular expressions For formula types, we can use new RegExp(obj.source, obj.flags) to copy a new regular expression object. For constructor types, we can use new obj.constructor( ) to copy a new constructor object.

Summary

Using JSON.parse(JSON.stringify(obj)) to implement deep copy is a simple and effective method, but it also has many limitations and problems that we need to pay attention to. These issues mainly include:

  • Cannot handle circular reference situations
  • Cannot handle values of types such as undefined and Symbol
  • Cannot handle values of Date, regular expression and other types
  • Cannot handle objects generated by constructors

In order to solve these problems, we can adopt the following solutions:

  • Use recursive method
  • Use third-party library methods
  • Use special handling methods