background

As you all know, basic data types in JavaScript, such as number, Boolean, string, NULL, and undefined, allocate independent memory when assigning values to variables. A compound type, such as Object, has referential variables, that is, a reference address that holds memory. Multiple variables may refer to the same memory address. In this way, when you modify one attribute of a variable, the attributes of other variables also change.

// Const a = 5;letb = a; b = 6; Console. log(b) // 6 console.log(a) // 5 // Reference type const person1 = {name:'tom'};let person2 = person1;
person2.name = 'jerry';

console.log(person1.name); // jerry
Copy the code

Therefore, this situation can sometimes cause data to interact with each other, leading to unexpected results. That’s where the deep copy of the object comes in. There are many ways to deep copy, and they are often asked in interviews. Today we will summarize the common deep copy implementations.

Use nested expansion operators

const original = {name: 'Jane', work: {employer: 'Acme'}}; const copy = {name: original.name, work: {... original.work}}; // Copy assert.deepEqual(original, copy); // True deep copy assert.ok(original.work! == copy.work);Copy the code

Through the JSON string

It’s a way of cutting corners, but it’s very quick. To make a deep copy of an object original, convert it to a JSON string, and then parse the JSON string:

function jsonDeepCopy(original) {
  return JSON.parse(JSON.stringify(original));
}
const original = {name: 'Jane', work: {employer: 'Acme'}};
const copy = jsonDeepCopy(original);
assert.deepEqual(original, copy);

Copy the code

The significant disadvantage of this approach is that you can only copy property names and values supported by the JSON format.

Unsupported attribute names and values are ignored:

assert.deepEqual(
  jsonDeepCopy({
    [Symbol('a')]: 'abc',
    b: function () {},
    c: undefined,
  }),
  {} // empty object
);

Copy the code

Other cases throw exceptions:

assert.throws(
  () => jsonDeepCopy({a: 123n}),
  /^TypeError: Do not know how to serialize a BigInt$/);

Copy the code

Implement universal deep copy

Here is a generic deep-copy function:

function deepCopy(original) {
  if (Array.isArray(original)) {
    const copy = [];
    for (const [index, value] of original.entries()) {
      copy[index] = deepCopy(value);
    }
    return copy;
  } else if (typeof original === 'object'&& original ! == null) { const copy = {};for (const [key, value] of Object.entries(original)) {
      copy[key] = deepCopy(value);
    }
    return copy;
  } else{// Base types do not need to be copiedreturnoriginal; }}Copy the code

This function handles three cases:

  • iforiginalIs an array, so we just create a new array and putoriginalI’m going to copy the elements in.
  • iforiginalFor this object, we use a similar method.
  • iforiginalIt’s a primitive type. We don’t have to do anything.

Try calling deepCopy():

const original = {a: 1, b: {c: 2, d: {e: 3}}}; const copy = deepCopy(original); // Is the copy as deep as the original? assert.deepEqual(copy, original); Assert. Ok (copy! == original); assert.ok(copy.b ! == original.b); assert.ok(copy.b.d ! == original.b.d);Copy the code

Note that deepCopy() solves only one problem with the expansion operator. Other problems remain: stereotypes are not copied, special objects are only partially copied, non-enumerable properties are ignored, and most properties are specifically ignored.

It’s almost impossible to make a universal full copy: not all data is tree-like, sometimes you don’t need to copy all attributes, etc.

A cleaner versiondeepCopy()

If you use.map() and object.fromentries (), the previous implementation of deepCopy() can be even simpler:

function deepCopy(original) {
  if (Array.isArray(original)) {
    return original.map(elem => deepCopy(elem));
  } else if (typeof original === 'object'&& original ! == null) {return Object.fromEntries(
      Object.entries(original)
        .map(([k, v]) => [k, deepCopy(v)]));
  } else{// Primitive values do not need to be copiedreturnoriginal; }}Copy the code

Implementing deep copy in a class (advanced)

Two techniques are commonly used to implement instance copies of classes:

  • .clone()methods
  • Copy constructor
.clone()methods

This technique introduces a.clone() method for classes that require a deep copy of their instance. It returns a deep copy of this. The following example shows three classes that can be copied.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  clone() {
    return new Point(this.x, this.y);
  }
}
class Color {
  constructor(name) {
    this.name = name;
  }
  clone() {
    return new Color(this.name);
  }
}
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);
    this.color = color;
  }
  clone() {
    return new ColorPoint(
      this.x, this.y, this.color.clone()); // (A)
  }
}

Copy the code

Annotated line A illustrates an important aspect of this technique: compound instance attribute values must also be copied recursively.

Static factory methods

A copy constructor is a constructor that initializes the current instance using another instance of the current class. Copy constructors are very popular in static languages such as C++ and Java. You can provide multiple versions of the constructor through static overloading. Static means it happens at compile time.

In JavaScript, you can do this (albeit inelegantly) :

class Point { constructor(... args) {if(args[0] instanceof Point) {const [other] = args; this.x = other.x; this.y = other.y; }else{ const [x, y] = args; this.x = x; this.y = y; }}}Copy the code

This class can be used as follows:

const original = new Point(-1, 4);
const copy = new Point(original);
assert.deepEqual(copy, original);

Copy the code

Instead, static factory methods in JavaScript are more appropriate. (Static means it’s a class method)

In the following example, the three classes Point, Color, and ColorPoint each have a static factory method.from() :

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  static from(other) {
    return new Point(other.x, other.y);
  }
}
class Color {
  constructor(name) {
    this.name = name;
  }
  static from(other) {
    return new Color(other.name);
  }
}
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);
    this.color = color;
  }
  static from(other) {
    return new ColorPoint(
      other.x, other.y, Color.from(other.color)); // (A)
  }
}

Copy the code

In commented line A, we use recursive copying again.

Colorpoint.from () can be used as follows:

const original = new ColorPoint(-1, 4, new Color('red'));
const copy = ColorPoint.from(original);
assert.deepEqual(copy, original);
Copy the code

Don’t tell the interviewer if you have this question in your next interview.

communication

Welcome to follow the wechat public number “1024 translator” to obtain the latest international Internet technology information.