JavaScript basics – deep and shallow copy

Welcome to star.

You are welcome to correct any mistakes.


Both shallow copy and deep copy are for reference types in JS. Shallow copy only copies references of objects. If the copied objects change, the original objects will also change. Only a deep copy is a true copy of an object.

preface

When it comes to deep copy, we must first mention the data type of JavaScript, the previous article JavaScript basic heart method – data type said very clear, here will not say more.

Just one thing to know: JavaScript data types are divided into basic data types and reference data types.

For copies of primitive data types, there is no difference between shallow copy and deep copy, which are all for reference data types.

Shallow copy

Shallow copy means that only references are copied without the actual values.

const originArray = [1.2.3.4.5];
const originObj = {a:'a'.b:'b'.c: [1.2.3].d: {dd:'dd'}};

const cloneArray = originArray;
const cloneObj = originObj;

console.log(cloneArray); / / [1, 2, 3, 4, 5]
console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}}

cloneArray.push(6);
cloneObj.a = {aa:'aa'};

console.log(cloneArray); / / [6]
console.log(originArray); / / [6]

console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}Copy the code

The code above is the simplest to implement a shallow copy using the = assignment operator. You can clearly see that originArray and originObj change as cloneArray and cloneObj change.

Deep copy

A deep copy is a full copy of the target, not just a layer of references, but also the values, as in a shallow copy.

As long as the deep copy, they will never communicate, no one will influence each other.

Currently, there are not many ways to implement deep copy. There are two main methods:

  1. usingJSONThe object of theparsestringify
  2. Objects are recreated and assigned at each level using recursion

JSON. Stringify/parse method

Let’s take a look at these two methods:

The JSON.stringify() method converts a JavaScript value to a JSON string.

Json. stringify converts a JavaScript value to a JSON string.

The JSON.parse() method parses a JSON string, constructing the JavaScript value or object described by the string.

Json. parse converts a JSON string to a JavaScript value or object.

It’s a simple conversion between JavaScript values and JSON strings.

Can it implement deep copy? Let’s try it.

const originArray = [1.2.3.4.5];
const cloneArray = JSON.parse(JSON.stringify(originArray));
console.log(cloneArray === originArray); // false

const originObj = {a:'a'.b:'b'.c: [1.2.3].d: {dd:'dd'}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false

cloneObj.a = 'aa';
cloneObj.c = [1.1.1];
cloneObj.d.dd = 'doubled';

console.log(cloneObj); / / {a: 'aa' b: 'b', c:,1,1 [1], d: {dd: 'doubled'}};
console.log(originObj); / / {a: 'a', b: 'b', c: [1, 2, 3], d: {dd: 'dd'}};Copy the code

It is indeed a deep copy, and very convenient. However, this method can only be used in some simple cases. For example, an object like the following does not apply:

const originObj = {
  name:'axuebin'.sayHello:function(){
    console.log('Hello World'); }}console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj); // {name: "axuebin"}Copy the code

In cloneObj, some attributes are missing… Why is that?

The reason is found in MDN:

If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in “pure” values like JSON.stringify(function(){}) or JSON.stringify(undefined).

Undefined, function, symbol ignored during conversion…

If an object contains a function (which is common), you cannot use this method for deep copy.

Recursive method

The idea of recursion is very simple, that is, each layer of data to implement a create object -> object assignment operation, simple and simple on the code:

function deepClone(source){
  const targetObj = source.constructor === Array ? [] : {}; // Determine whether the target of replication is an array or an object
  for(let keys in source){ // Iterate over the target
    if(source.hasOwnProperty(keys)){
      if(source[keys] && typeof source[keys] === 'object') {// If the value is an object, recurse
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
      }else{ // If not, assign directlytargetObj[keys] = source[keys]; }}}return targetObj;
}Copy the code

Let’s try:

const originObj = {a:'a'.b:'b'.c: [1.2.3].d: {dd:'dd'}};
const cloneObj = deepClone(originObj);
console.log(cloneObj === originObj); // false

cloneObj.a = 'aa';
cloneObj.c = [1.1.1];
cloneObj.d.dd = 'doubled';

console.log(cloneObj); / / {a: 'aa' b: 'b', c:,1,1 [1], d: {dd: 'doubled'}};
console.log(originObj); / / {a: 'a', b: 'b', c: [1, 2, 3], d: {dd: 'dd'}};Copy the code

You can. Then try one with a function:

const originObj = {
  name:'axuebin'.sayHello:function(){
    console.log('Hello World'); }}console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = deepClone(originObj);
console.log(cloneObj); // {name: "axuebin", sayHello: ƒ}Copy the code

Can also. Get things done.

Do you think this is the end? Of course not.

Copy methods in JavaScript

We know that in JavaScript, there are two methods for arrays: concat and slice, which make a copy of the original array. Neither method changes the original array, but returns a new modified array.

At the same time, ES6 introduced object.assgn methods and… The expansion operator can also copy objects.

Are they shallow copies or deep copies?

concat

The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.

This method can concatenate two or more arrays, but instead of modifying an existing array, it returns a new one.

It looks like a deep copy. Let’s try it:

const originArray = [1.2.3.4.5];
const cloneArray = originArray.concat();

console.log(cloneArray === originArray); // false
cloneArray.push(6); / / [6]
console.log(originArray); [1.2.3.4.5];Copy the code

Looks like a deep copy.

Let’s think about what happens if this object is multi-layered.

const originArray = [1[1.2.3] and {a:1}];
const cloneArray = originArray.concat();
console.log(cloneArray === originArray); // false
cloneArray[1].push(4);
cloneArray[2].a = 2; 
console.log(originArray); / / [1, 1, 2, 3, 4, 2} {a:]Copy the code

An originArray contains arrays [1,2,3] and objects {a:1}. If you modify arrays and objects directly, it does not affect the originArray. However, if you modify arrays [1,2,3] or objects {a:1}, you will find that the originArray changes.

Conclusion:concatIt just makes a deep copy of the first layer of the array.

slice

The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.

Shallow copy (shallow copy

But, no!

const originArray = [1.2.3.4.5];
const cloneArray = originArray.slice();

console.log(cloneArray === originArray); // false
cloneArray.push(6); / / [6]
console.log(originArray); [1.2.3.4.5];Copy the code

Similarly, let’s try a multi-tiered array.

const originArray = [1[1.2.3] and {a:1}];
const cloneArray = originArray.slice();
console.log(cloneArray === originArray); // false
cloneArray[1].push(4);
cloneArray[2].a = 2; 
console.log(originArray); / / [1, 1, 2, 3, 4, 2} {a:]Copy the code

Sure enough, the results were the same as concat.

Conclusion:sliceIt just makes a deep copy of the first layer of the array.

Object.assign()

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.

Copy, copy, copy.

Is it a shallow copy or a deep copy?

Try it yourself.

Conclusion:Object.assign()I’m copying the property value. If the attribute value of the source object is a reference to the object, it copies only that reference value.

. Expansion operator

const originArray = [1.2.3.4.5[6.7.8]].const originObj = {a:1.b: {bb:1}};

const cloneArray = [...originArray];
cloneArray[0] = 0;
cloneArray[5].push(9);
console.log(originArray); / / [1, 2, 3, 4, 5, 6,7,8,9 []]

constcloneObj = {... originObj}; cloneObj.a =2;
cloneObj.b.bb = 2;
console.log(originObj); // {a:1,b:{bb:2}}Copy the code

Conclusion:.The implementation is a deep copy of the first layer of the object. What follows is just the copied reference value.

First shallow copy

We know that there is a situation where the first layer of the target object is made a deep copy, followed by a shallow copy, which can be called a “shallow first layer copy”.

We can implement a function like this ourselves:

function shallowClone(source) {
  const targetObj = source.constructor === Array ? [] : {}; // Determine whether the target of replication is an array or an object
  for (let keys in source) { // Iterate over the target
    if(source.hasOwnProperty(keys)) { targetObj[keys] = source[keys]; }}return targetObj;
}Copy the code

Let’s test it out:

const originObj = {a:'a'.b:'b'.c: [1.2.3].d: {dd:'dd'}};
const cloneObj = shallowClone(originObj);
console.log(cloneObj === originObj); // false
cloneObj.a='aa';
cloneObj.c=[1.1.1];
cloneObj.d.dd='surprise';Copy the code

CloneObj {a:’aa’,b:’b’,c:[1,1,1],d:{dd:’surprise’}} If cloneObj === originObj is false, it does not affect originObj.

console.log(cloneObj); / / {a: 'aa' b: 'b', c:,1,1 [1], d: {dd: 'surprise'}}
console.log(originObj); / / {a: 'a', b: 'b', c: [1, 2, 3], d: {dd: 'surprise'}}Copy the code

What happend?

OriginObj does not affect a, c, but an object in D is modified… What about the deep copy? Isn’t the reference address different?

So this is it:

  1. fromshallowCloneAs we can see from the code, we only carried out for the first level of the targetDeep copyAnd the second layer starts with a target that we use directly=Assignment operator to copy.
  2. So, the target after the second layer only copies a reference, that is, a shallow copy.

conclusion

  1. The assignment operator=The implementation is shallow copy, only copy the object reference value;
  2. The native copy method for arrays and objects in JavaScript is “shallow first-layer copy”.
  3. JSON.stringifyDeep copy is implemented, but there are requirements for the target object;
  4. For true deep copy, recurse.