Welcome to “Deep Into The Essence” series, in which Blue will lead you to explore the essence behind the code, get a glimpse of the author’s thoughts and intentions behind the code, and experience the beauty of ingenious ideas and design beneath the essence

To understand why things are the way they are, put yourself in the shoes of the creator

– Blue

Deep clone, or deep copy, is a common technique used in job interviews, but what exactly is it? How to do? What are the notable issues? Blue shows you around

There are a lot of names for this — deep clone, deep clone, deep copy, deep copy, deep copy, deep copy, etc., but the meaning is the same, and Blue will use the term “deep clone” intensively in this article

This article will cover the following

  • Deep clone, shallow clone has why distinction
  • Why and when should hyperclone
  • Memory allocation for data in JS — by value and by ref
  • How are clone operations done for various data types
  • How do you complete a deep clone

Cloning? Deep cloning? What you mean

The so-called clone, in fact, is replication, we often need to copy the object, so that the change does not affect the original object, JS common clone has two kinds:

  • Shallow copy: Copies only the object itself, not the content
  • Deep copy: Copies both the object itself and its contents (and “contents of contents”)

Let’s look at an example

let a={
  name: 'blue'.datas: [12.5.8]};// Shallow clone - copies only the object itself
let b=shallowClone(a); // An imaginary function, which will be implemented later

/ / modify b
b.datas.push(99);

console.log(a); //{name: 'blue',datas: [12, 5, 8, 99]} Problem: The original object also changed
console.log(b); //{name: 'blue',datas: [12, 5, 8, 99]}
Copy the code

The shallow clone copies only the object itself, and the datas still share an array object, so it modifies the DATAS of B, causing a to change as well

Let’s see another one

let a={
  name: 'blue'.datas: [12.5.8]};// Deep clone -- copy the entire object (object + content)
let b=deepClone(a); // An imaginary function, which will be implemented later

/ / modify b
b.datas.push(99);

console.log(a); //{name: 'blue',datas: [12, 5, 8]} does not affect the original object
console.log(b); //{name: 'blue',datas: [12, 5, 8, 99]}
Copy the code

The difference between deep clone and shallow clone

We may think: that deep cloning must be good, all new, no impact on the previous, shallow cloning will also put the previous object, I will use the deep cloning later! In fact, no, there is almost no absolute good or bad program, mainly depends on whether it is suitable, let’s compare

advantages disadvantages Applicable scenario
A deep clone Complete data isolation, never affecting the original object Poor performance because there are more things to copy Perform any operation without affecting the original data

For example: form parameter transfer (modify form data without affecting the original data)
Shallow clone High performance There is no complete quarantine Manipulate only surface data

Such as: data sorting, movement, exchange and other operations

Here, we should be clear, deep cloning is the object complete clone again, object itself and content are cloned, so how to do? Let’s get started

Start from scratch – values and references

In almost every language, there are two ways to use data, values and references:

  • value: a variable stores the value itself, such as numbers, booleans, etc
  • referenceA variable stores not the data itself, but the “address” of the data, such as: JSON, array, etc

This issue is so important, not just for cloning, but for other issues, that Blue takes you through it with a look at two examples:

// Save the value of the case
let a=12;
let b=a; // Note: number -> save value

b++;

console.log(a, b); //12, 13 do not affect the original data
Copy the code

// The reference condition
let a=[1.2.3];
let b=a; // Note: object -> Save references

b.push(5);

console.log(a, b); //[1,2,3,5] changes the original object
Copy the code

What exactly is a quote?

First of all, I want you to remember this, and we’ll understand it in a second — assignment must copy data, but what copy is different. What does that mean?

  • Storing value data (e.g. numbers), storing the data itself, so replication is also copying the data itself
  • Store the referenced data (for example, arrays), store the reference, copy the reference, but the data itself is the same copy

The value (by value)

Pass value – Variables store the data itself directly

Passing references (by ref)

Pass references – Variables store the address of the data (both references), and replication copies that address, not the object itself

conclusion

  • Assignment always copies the contents of a variable — but what
  • ByVal means that the variable stores the data itself, and when assigned, the data itself is copied
  • Addressing/referential (byRef) means that variables store references/addresses, and when assigned, the address is copied, but the referenced object is the same

How do I copy a reference object?

If you want to copy/clone a reference, you cannot assign it directly. You need to do this manually. Let’s see Blue give you two little examples to make it clear

let a=[1.2.3];
let b=a; // A and B both refer to the same object in the wrong way

b.push(5);
console.log(a, b); / /,2,3,5 [1] [1,2,3,5]
Copy the code

Copy the array

There are a lot of ways to copy an array, but the core is the same — you create a new array, so you have two different arrays

1. Basic (and essential)

let a=[1.2.3];
let b=[]; // Equivalent to new Array(), create a new Array

// Get a's stuff
for(let i=0; i<a.length; i++){ b[i]=a[i];//a[I] is some number (pass value)
}

// Try modifying b
b.push(5);

console.log(a);
console.log(b);
Copy the code

Mode 2. Generic – Expand operation (…)

let a=[1.2.3];
let b=[...a]; // In fact, it is also a loop, you do not need to write

b.push(5);

console.log(a);
console.log(b);
Copy the code

Method 3. General-stringify +parse

let a=[1.2.3];

let str=JSON.stringify(a); / / [1, 2, 3] ""
let b=JSON.parse(str); //[1,2,3] is a new object because it is created from a string and has nothing to do with the original object

// If necessary, we can also abbreviate:
let b=JSON.parse(JSON.stringify(a));

b.push(5);

console.log(a);
console.log(b);
Copy the code

By the way, Stringify + Parse looks beautiful, but it has a lot of problems, like not all data types accept and circular references can be problematic, so use with caution

let a=[
  1.2.3.function (){
    console.log(this.length); }];// Note that the function cannot stringify
let b=JSON.parse(JSON.stringify(a));

console.log(b);
Copy the code

Method 4. Partial array methods

let a=[1.2.3];

let b=[].concat(a);  // Create a new array and concatenate the contents of a
let c=a.map(i= >i);   // Map creates a new array when it maps, and then we take everything back as is
let d=a.filter(_= >true);  // Filter will also create new ones and always return true, i.e., all of them
let e=a.slice(0, a.length);  // Create a subarray from array A (0~length-1)
// There are many........
Copy the code

There are so many examples of evil gameplay that I don’t need to mention them because they are not practical. convenient

Copying json objects

Json is similar in nature to arrays and has a number of generic methods

Method 1: The most basic, the most essential

let a={name: 'blue'.age: 18};
let b={}; // New object, not related to a

for(let key in a){
  b[key]=a[key]; // Bring it over
}

/ / modify b
b.age++;

console.log(a);
console.log(b);
Copy the code

Mode 2. General – Expand operation

let a={name: 'blue'.url: 'zhinengshe.com'};
letb={... a};// The same is true for loop

b.age=18;

console.log(a); //{name, url}
console.log(b); //{name, url, age}
Copy the code

Method 3. General-stringify +parse

Arrays can become strings, as can JSON, so Stringify + Parse actually works with JSON objects as well

let a={name: 'blue'.url: 'zhinengshe.com'};
let b=JSON.parse(JSON.stringify(a));

b.age=18;

console.log(a); //{name, url}
console.log(b); //{name, url, age}
Copy the code

Method 4. object. assign method

let a={name: 'blue'.age: 18};
let b=Object.assign({}, a); // Essentially, create a new object {} and assign all the attributes.

b.age++;

console.log(a); //{age: 18}
console.log(b); //{age: 19}
Copy the code

Copy other objects

For a long time, this is actually the difficulty, where is the difficulty?” There are too many other objects — Date, RegExp, ArrayBuffer,…. “, but also the user defined, there is almost no way to deal with all of them, let’s try a few to find a sense

Copy the Date object

// Date is a reference. What if it isn't (hehe)
let date1=new Date('2020-1-1');
let date2=date1;

date2.setDate(30);

console.log(date1); //1 30 2020
console.log(date2); / / 1 to 30, 2020
Copy the code

(Oh hoo, shit)

Date is also a reference, so you must create a new Date instance to complete the copy.

let date1=new Date('2020-1-1');
let date2=new Date(date1.getTime()); // At its simplest, we can create 2 from date1's time (a number, not a reference)

date2.setDate(30);

console.log(date1);
console.log(date2);
Copy the code

But the question is, getTime is a Date specific operation, is there a more general operation? Yes, here’s Blue:

let a=new Date('2020-1-1'); //1. First, we have an object, A, which we want to copy
let b=new Date(a); / / 2. Huh? What the...

b.setDate(30); //3. What about a

console.log(a);
console.log(b);
Copy the code

Um… What happened? It worked. What the hell did we do

Look at the

There are two basic methods of creating all system objects:

  • Blank object: Creates an object entirely new, such as:new Date()
  • Create from an existing instance: From the value of an instancecopyGive another example, such as:new Date(date1)

There are many similar examples:

let map1=new Map(a); map1.set('a'.12); // Add whatever you want
map1.set('b'.5);

let map2=new Map(map1); / / copy map1
map2.set('c'.99);


console.log(map1);
console.log(map2);
Copy the code

That’s, like, interesting. Let’s try a few more

let arr1=new Uint8Array([0.0.0.0.0.0.0.0.0]);
arr1.fill(25.2.5); // Fill in 25 for positions 2 to 5(excluding 5 itself)

console.log(arr1); //[0, 0, 25, 25, 25, 0, 0, 0, 0]


let arr2=new Uint8Array(arr1);
arr2.fill(11.1.6);

console.log(arr1); //[0, 0, 25, 25, 25, 0, 0, 0, 0]
console.log(arr2); //[0, 11, 11, 11, 11, 0, 0, 0]
Copy the code

So, the system object is easy to handle, just new, but…

What about custom classes?

It’s really easy, as long as your class follows this method. Here’s an example

// Create a custom class
class Person{
  constructor(user, age){
    this.user=user;
    this.age=age;
  }
  
  sayHi(){
    console.log(`My name is The ${this.user}.The ${this.age} years old.`); }}// Use it casually
const p1=new Person('blue'.18);

p1.sayHi();
Copy the code

Let’s modify it to take another Person instance as an argument:

class Person{
  constructor(user, age){
    // Here is the point
    if(arguments.length==1 && arguments[0] instanceof Person){
      // Take the value from an existing instance
      this.user=arguments[0].user;
      this.age=arguments[0].age;
    }else{
      // The same as before
      this.user=user;
    	this.age=age; }}sayHi(){
    console.log(`My name is The ${this.user}.The ${this.age} years old.`); }}// Try it if it works
let p1=new Person('blue'.18);
let p2=new Person(p1);

p2.age+=5;

p1.sayHi(); / /... 18 years old
p2.sayHi(); / /... 23 years old
Copy the code

This makes sense, so can we apply this method directly to arrays and JSON? How convenient… A ghost ah 😂

Can arrays work?

If only it could be used… Give it a try

let arr1=[1.2.3];
let arr2=new Array(arr1); / / copy arr1

// Don't change anything, try it first
console.log(arr1); / / [1, 2, 3]
console.log(arr2); / / [[1, 2, 3]] HMM? What the devil
Copy the code

(Browser output is not good, Node is used)

Why is that?

It actually depends on the parameters of Array

console.log(new Array(1.2.3)); / / [1, 2, 3]
console.log(new Array('abc')); //['abc']
console.log(new Array([12.5])); / / [[12, 5]]
Copy the code

An Array argument is its initial data (except an integer).

Do JSON objects work?

No, let’s just look at an example

let obj1={a: 12.b: 5};
let obj2=new Object(obj1);

console.log(obj1); //{a: 12, b: 5}
console.log(obj2); //{a: 12, b: 5} teacher, you are wrong!! This is not ok !!!!!


// Give it a try
obj2.b++;

console.log(obj1); //{a: 12, b: 6}
console.log(obj2); //{a: 12, b: 6} um ?????????
Copy the code

New Object(obj) does not create a new Object, but returns the original Object.

let obj2=new Object(obj1);
// It is exactly equivalent to
let obj2=obj1;
Copy the code

What the hell is this…

summary

So, we can do it in five different ways

  • Basic types (numbers, strings, Booleans) that need no processing
  • Json /object can be used.
  • Array, you can use.
  • Other system objects, such as Date, cannew xxx(old)
  • User-defined objects, such as Person, need to be handled by constructor

At this point, we can write a simple Clone method, but for now shallow clones (deep clones in a moment).

function clone(src){ // Note: shallow clone version
  if(typeofsrc! ='object') {// Basic type, no clone
    return src;
  }else{
    // There are three object types: JSON, array, system type, and user type
    if(src instanceof Array) {/ / 1 - array
      return [...src];
    }else if(src.constructor===Object) {// 2-object, which is json
      return{... src}; }else{ //3- System object or user-defined object (need constructor support)
      return new src.constructor(src); // Use its own constructor to create a copy of it}}}Copy the code

That’s it. Let’s test it out

/ / 1 - array
let arr1=[1.2.3];
let arr2=clone(arr1);

arr2.push(55);

console.log(arr1);
console.log(arr2);

//2-json
let json1={a: 12.name: 'blue'};
let json2=clone(json1);

json2.age=18;

console.log(json1);
console.log(json2);


/ / 3 - system class
let date1=new Date('1990-12-31');
let date2=clone(date1);

date2.setFullYear(2020);

console.log(date1);
console.log(date2);
Copy the code

I passed all the tests. No problem

How do you do deep cloning?

We’ve already done shallow clones, so that’s one more step, but let’s be clear: why isn’t clone deep? Because it only copies the first layer. Let’s do an example

// If only one layer is ok, how about more layers?
let arr1=[
  1.2,
  {a: 12.b: 5}];let arr2=clone(arr1);

console.log(arr1);
console.log(arr2);
Copy the code

It looks good. No problem. Really?

Let’s try something different

let arr1=[
  1.2,
  {a: 12.b: 5}];let arr2=clone(arr1);

arr1[0] + +;// Note: this is layer 1

console.log(arr1);
console.log(arr2);
Copy the code

It seems to be ok, yes, because we haven’t changed the contents yet, the array itself is copied, of course it’s ok

Take a look inside:

let arr1=[
  1.2,
  {a: 12.b: 5}];let arr2=clone(arr1);

arr1[2].b=99; // Layer 2: json objects in the array

console.log(arr1);
console.log(arr2);
Copy the code

finished

Why ah?

Because you really only copied one layer, and the JSON inside is the same object, let’s analyze it

let arr1=[
  1.2,
  {a: 12.b: 5}];let arr2=clone(arr1);
// It is equivalent to... "Because that's how clone works
let arr2=[...arr1];
/ / but... In other words, it's equivalent to
let arr2=[];
arr2[0]=arr1[0]; //1, ok, basic type
arr2[1]=arr1[1]; //2, that's fine
arr2[2]=arr1[2]; //{a: 12, b: 5}, the json assignment is not copied at all
Copy the code

What can we do?

In short, we need to clone not only the outer layer of the object, but also the inner object. The problem is that we do not know how many layers the object has. Using a recursive

All right, let’s take a look at the finished product

function deepClone(src){
  if(typeofsrc! ='object') {// Basic type, no clone
    return src;
  }else{
    if(src instanceof Array) {//return [...src]; Because we're going to clone each one, so discard... Do your own loop
      let result=[];
      for(let i=0; i<src.length; i++){//result[i]=src[i]; If I write it this way, the inner layer is still not copied. Wrong
        result[i]=deepClone(src[i]); Clone the inner layer once, and you are done
      }
      return result;
    }else if(src.constructor===Object) {//return {... src}; Just like an array, we'll copy it ourselves, discard it...
      let result={};
      for(let key in src){
        result[key]=deepClone(src[key]); Clone the inner layer once
      }
      return result;
    }else{ //3- System object or user-defined object (need constructor support)
      return new src.constructor(src); // Use its own constructor to create a copy of it}}}Copy the code

The idea is very simple. Let me repeat:

  • If you hit a basic type, you return it, because it’s byValue and you don’t need to deal with it
  • If you come across a JSON/array, make a copy of it and build a new array (there may be other arrays, json, recursion).
  • If you encounter system objects and user-defined objects, let them handle themselves (custom objects need constructor)

That’s a lot of work. Let’s try it out

// Add more layers
let obj1={
  name: 'blue'.items: [{type: 'website'.value: 'zhinengshe.com'},
    {type: 'favorites'.value: 2},]};let obj2=deepClone(obj1);

// Try something different
obj2.items[1].value=99;

console.log(obj1);
console.log(obj2);
Copy the code

This result is relatively safe

But wait…

Why didn’t you say 😂 earlier

Okay, so I’m going to tell you a scary fact (no) :

For the most part, we’ll just stringify+parse

Stringify +parse we said that it has a range of applications: Functions don’t work, dates don’t work, this doesn’t work, that doesn’t work, but have you ever thought that most of the time, we don’t actually have any of these things, just pure data (numbers, strings, booleans, arrays, JSON, etc.), so these two can solve most of the problems

Let’s look at an example

let obj1={
  name: 'blue'.items: [{type: 'website'.value: 'zhinengshe.com'},
    {type: 'favorites'.value: 2},]};let obj2=JSON.parse(JSON.stringify(obj1)); / / in this goods

// Try something different
obj2.items[1].value=99;

console.log(obj1);
console.log(obj2);
Copy the code

So the conclusion is: most of the time stringify+parse, use deepClone for special data (Date, TypedArray, etc.), and we’re done

conclusion

It’s time to go over what Blue said, so first of all

  • Proclone complete replication; Shallow clone has high performance
  • There are two ways to store data in JS (and most languages) — values and references
    • Value: Stores the data itself, including all basic types
    • Reference: Address where data is stored, including all objects
  • valueWhen assigning, it copies;referenceWhen assigning, the data itself is not copiedManually copy
  • When we copy, we have to do it case by case
    • Basic type: Never mind
    • Array, JSON: looping or…
    • System type: Copy using the constructornew Date(old)
    • User type: Need to embed your own code ahead of time in Constructor
  • Deep clone is to recursively clone each data layer by layer
  • For the most part, stringify+parse will do, unless there is a special type (Date or something) that requires a deepClone

Have a bug? Would like to add?

Thank you for watching this tutorial. If you have any questions or want to communicate with Blue, please leave a comment directly. If you find anything inappropriate in this article, please also point it out