Recently, I learned about deep copy and shallow copy of JavaScript objects. I made some comparisons between the actual development points and the basic points. Without further ado, I started to get into the topic.


  • Basic knowledge — Basic types
  • Basic knowledge — reference types
  • Shallow copy implementation – object && array
  • Deep copy implementation – object && array
  • Implementation of deep copy – ES6 extension operator to implement deep copy of object && array
  • Deep copy implementation – recursive approach
  • Deep copy implementation – json.stringify /parse method

Basic Knowledge:

There are five basic data types in JavaScript (i.e., simple data types). They are: Undefined, Null, Boolean, Number and String, and the basic types are stored in stack memory. It also contains a complex data type (also known as a reference type) stored in heap memory, called Object (Array). Heap memory is used to hold objects created by new, and stack memory is used to hold variables of basic types and references to objects.

Note the difference between Undefined and Null. Undefined has only one value, which is Undefined, and Null has only one value, which is Null

Undefined is the output of a declared unassigned variable

Null is simply the result of an object that does not exist.

JS shallow copy and deep copy, only for complex data type (Object, Array) replication problem. Both shallow and deep copies can reproduce a copy of an existing object. However, instances of objects are stored in heap memory and manipulated by a reference value, so there are two types of copying: copying references and copying instances, which is the difference between shallow and deep copies

Copy the code

1. For basic data types

Their values occupy a fixed amount of memory and are stored in stack memory. When a variable copies a value of the primitive type to another variable, a copy of that value is created, and we cannot add attributes to values of the primitive data type

var a = 1;
var b = a;
b.name = 'hanna';
console.log(a); //1
console.log(b.name); //undefined
Copy the code

In the code above, A is the basic data type (Number), and B is a copy of A. They both have the same amount of memory in different locations, except that they have the same value, and if you change one of them, the other doesn’t change.

2. For reference types

A complex data type is a reference type, whose value is an object, stored in heap memory, and whose variable containing the value of the reference type actually contains not the object itself, but a pointer to that object. Copying a reference type value from one variable to another is actually copying the address of the pointer, so both variables end up pointing to the same object

var obj = {
   name:'Hanna Ding',
   age: 22
}
var obj2 = obj;
obj2['c'] = 5;
console.log(obj); //Object {name: "Hanna Ding", age: 22, c: 5}
console.log(obj2); //Object {name: "Hanna Ding", age: 22, c: 5}
Copy the code

Note: 0x123 pointer address

We can see that when we assign obj to obj2, we change the property value of one of the objects, and both objects change. The root cause is that obj and obj2 both point to the same pointer. When we assign, we just copy the address of the pointer. So when we change the value of one variable it affects the value of the other variable.

The difference between deep copy and shallow copy

Shallow copy: Copies only Pointers to an object but not the object itself. The old and new objects share a chunk of memory. Deep copy: Copy and create an identical object, without sharing memory, modify the new object, leaving the old object unchanged.

Shallow copy implementation

Shallow copy means just copying the reference without copying the actual value. Sometimes we just want to back up the array, but simply assign it to a variable, change one of them, and then change the other one, but a lot of times that’s not what we want.

(1) Shallow copy of an object: the code above is an example of a shallow copy of an object

(2) Shallow copy of array:

var arr = [1, 2, 3, '4'];

var arr2 = arr;
arr2[1] = "test"; console.log(arr); / / [1,"test", 3, "4"] console.log(arr2); / / [1,"test", 3, "4"]

arr[0]="fisrt"console.log(arr); / / /"fisrt"."test", 3, "4"] console.log(arr2); / / /"fisrt"."test", 3, "4"]



Copy the code

The above code is the simplest implementation of a shallow copy using the = assignment operator. It is clear that as arr2 and ARR change, arR and ARR2 change as well

Deep copy implementation

For arrays we can use the slice() and concat() methods to solve the above problem

Note :(slice() and concat() are limited for deep copies of arrays.)

**slice **

var arr = ['a'.'b'.'c'];
var arrCopy = arr.slice(0);
arrCopy[0] = 'test'console.log(arr); / / /"a"."b"."c"] console.log(arrCopy); / / /"test"."b"."c"]
Copy the code

concat

var arr = ['a'.'b'.'c'];
var arrCopy = arr.concat();
arrCopy[0] = 'test'console.log(arr); / / /"a"."b"."c"] console.log(arrCopy); / / /"test"."b"."c"]
Copy the code

For the slice() and concat() pair limitations mentioned above, we can continue with the following example:

var arr1 = [{"name":"Roubin"}, {"name":"RouSe"}]; Var arr2 = []. Concat (arr1); // Copy array arr1[1]. Name ="Tom"; console.log(arr1); / / [{"name":"Roubin"}, {"name":"Tom"}] console.log(arr2); / / [{"name":"Roubin"}, {"name":"Tom"}]
Copy the code

You can see that using.concat() gives the same results as using shallow copy. Why? Does slice () give the same results? Let’s go ahead and write some examples

var arr1 = [{"name":"weifeng"}, {"name":"boy"}]; Var arr2 = arr1.slice(0); // Copy array arr1[1]. Name ="girl"; console.log(arr1); / / [{"name":"weifeng"}, {"name":"girl"}] console.log(arr2); / / [{"name":"weifeng"}, {"name":"girl"}
Copy the code
var a1=[["1"."2"."3"]."2"."3"]; var a2=a1.slice(0); a1[0][0]=0; // Change the first element in the first element of a1 console.log(a1); / / [["0"."2"."3"]."2"."3"] console.log(a2); / / [["0"."2"."3"]."2"."3"]
Copy the code

Object, Array, slice, and concat are copies of Object, Array, slice, and concat. The entire copy is a shallow copy, and the Pointers to each value of the Array will still point to the same storage address.

Thus, slice and concat are only good for deep copies of one-dimensional arrays that do not contain reference objects

Note (Supplementary points) :

  • ArrayObj. Slice (start, [end]) This method returns an Array object containing the specified portion of arrayObj. It doesn’t change the original array
  • The arrayobj.concat () method is used to join two or more arrays. This method does not alter the existing array, but simply returns a copy of the concatenated array. In fact, that is the way to achieve the following, but still use the above method to achieve more simple and efficient
function deepCopy(arr1, arr2) {
   for(var i = 0; i < arr1.length; ++i) { arr2[i] = arr1[i]; }}Copy the code

The ES6 extension operator implements deep copies of arrays

Var arr = [1, 2, 3, 4, 5] var arr2 [...] = arr arr. [2] = 5 console log (arr) / /,2,5,4,5 [1] the console log (arr2) / / [1, 2, 3, 4, 5]Copy the code

(2) Object deep copy the realization principle of object deep copy: define a new object, traverse the properties of the source object and assign the properties to the new object are mainly two kinds:

  • Objects are recreated and assigned at each level using recursion
  • Leverage Parse and Stringify in JSON objects
  • The ES6 extension operator implements deep copies of objects
var obj = {
  name: 'FungLeo',
  sex: 'man',
  old: '18'} var { ... obj2 } = obj obj.old ='22'
console.log(obj)   ///{ name: 'FungLeo', sex: 'man', old: '22'}
console.log(obj2)  ///{ name: 'FungLeo', sex: 'man', old: '18'}
Copy the code
Copy the code
var obj = {
   name:'xiao ming',
   age: 22
}

var obj2 = new Object();
obj2.name = obj.name;
obj2.age = obj.age

obj.name = 'xiaoDing';
console.log(obj); //Object {name: "xiaoDing", age: 22}
console.log(obj2); //Object {name: "xiao ming", age: 22}
Copy the code

Obj2 is a new memory block created in the heap. When assigning obj1’s properties to obj2, obj2 is the memory address corresponding to direct access.

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 objectfor(let keys in source){// Iterate over the targetif(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, specify targetObj[keys] =source[keys]; }}}return targetObj;
}
Copy the code

Let’s try an example:

var obj = {
    name: 'Hanna',
    age: 22
}
var objCopy = deepClone(obj)
obj.name = 'ding'; console.log(obj); //Object {name:"ding", age: 22} console.log(objCopy); //Object {name:"Hanna", age: 22}

Copy the code

Objects convert to Json

Let’s start with two methods: son.stringify /parse

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.

Parse ** converts a JSON string to a JavaScript value or object.

Conversion between JavaScript values and JSON strings.

Take a step-by-step look at the following encapsulation layer example:

function  deepClone(origin){
    var clone= {}; try{clone= JSON.parse(JSON.stringify(origin));
    }
    catch(e){
        
    }
    return clone;

}
Copy the code

Unencapsulated versus encapsulated:

Const originArray = [1, 2, 3, 4, 5]; constcloneArray = 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';
clone,1,1 Obj. C = [1];cloneObj.d.dd = 'tt';
 
console.log(cloneObj); console.log(originObj); /**************** Encapsulation layer **************/function  deepClone(origin){
    var  clone= {}; try{clone= JSON.parse(JSON.stringify(origin));
    }
    catch(e){
        
    }
    return clone;

}
const originObj = {a:'a',b:'b', c: [1, 2, 3], d: {dd:'dd'}};
const cloneObj = deepClone(originObj);
console.log(cloneObj === originObj); // false/ / change the valuecloneObj.a = 'aa';
cloneObj. C = (4 and 6);cloneObj.d.dd = 'tt';

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

Copy the code

While the deep copy above is convenient (use wrapping functions for project development for maintenance), it is only suitable for simple scenarios (Number, String, Boolean, Array, Object), flat objects, and data structures that can be directly represented by JSON. Function objects, RegExp objects are not deep-copied in this way.

Note:

var  clone= {}; try{clone= JSON.parse(JSON.stringify(origin)); }} I made a mistake in writing code: using const continuous declarations causes the code to run incorrectly because continuous const declarations in the same code result in a temporary dead zone. Detailed please see nguyen half-stretching ES6] [(http://es6.ruanyifeng.com/# docs/let# const - command)
    const clone= {}; try{ constclone= JSON.parse(JSON.stringify(origin));
    }
    
Copy the code

For example:

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

In cloneObj, some attributes are missing… What’s the reason? 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.