Copy, shallow copy, and deep copy of objects in JavaScript

Copy, shallow copy, and deep copy of objects in JavaScript

I encountered copying problems in development before, and I also saw the assignment-related problems written by students who just started using Vue when developing. When using it, the concept makes the assignment data unclear and obscure. In order to help others or help yourself to remember more deeply, after consulting relevant materials, record your little insights here. Before we figure out the copy, we first figure out the relationship between the data type and the stack

The relationship between data type and stack

JS data type: How many data types are there in JS?

8 Number String Boolean Null undefined object symbol bigInt
 
Symbol

Symbol is essentially a unique identifier, which can be used as the unique property name of an object, so that others will not overwrite or overwrite the property value you set

The characteristic of the Symbol data type is uniqueness, even if the value generated by the same variable is not equal

let id1 = Symbol('id'); 
let id2 = Symbol('id'); 
console.log(id1 == id2);//false
 

Another feature of the Symbol data type is its concealment. For in, object.keys() cannot be accessed

let id = Symbol("id"); 
let obj = { [id]:'symbol' }; 
for(let option in obj){ 
    console.log(obj[option]);// 
}
 

But there are also methods that can be accessed: Object.getOwnPropertySymbols , the Object.getOwnPropertySymbols method will return an array, the members are all Symbol values of the current object used as property names

JS data types: Which types are included in Object?

  Object, Array, Date, Function, RegExp 
 

JS data types: What are the basic types of JS?

 String Number boolean null undefined
 
BigInt

A kind of bigInt appeared in Google version 67, which refers to safe storage and manipulation of large integers. (But many people don't regard this as a type) BigIntThe purpose of the Numberdata type is to have a larger range of integer values than the data type supports. When performing mathematical operations on large integers, the ability to represent integers with arbitrary precision is particularly important. Use BigInt, integer overflow will no longer be a problem. For details, please see JavaScript standard built-in objects , J S latest basic data type: BigInt

Storage method

 ` ` ` ` 
    
 ` ` ( ) 
 

Here is a picture showing the relationship between them previously stored

Closures and heap memory

The variables in the closure are not stored in the middle stack memory, but in the heap memory . This also explains why the closure can still refer to the variables in the function after the function is called. Let's first look at what a closure is:

function A() {
  let a = ' '
  function B() {
      console.log(a)
  }
  return B
}
 

Function A returns a function B, and the variables of function A are used in function B, function B is called a closure. After function A is called, the variables in function A are stored on the heap at this time, so function B can still refer to the variables in function A

Assignment

Basic data type copy

let a =' ';
let b = a;
b='www.vipbic.com' 
console.log(a);// 
 

Conclusion : When the data in the stack memory changes, the system will automatically assign a new value to the new variable in the stack memory. The two variables are independent of each other and do not affect each other.

Reference data type copy

let a = {x:' ', y:' 1'}
let b = a;
b.x = 'www.vipbic.com';
console.log(a.x);//www.vipbic.com
 

Conclusion The copy of the reference type also assigns a new value to the new variable b and saves it in the stack memory. The difference is that the specific value corresponding to this variable is not in the stack, but just an address pointer in the stack. The two variable address pointers are the same, pointing to the object in the heap memory, so when bx changes, ax also changes

Shallow copy

Array's slice and concat methods

The slice and concat methods of Array do not modify the original array, but only return a new array that shallowly copies the elements in the original array. The reason why it is placed in a shallow copy is because it looks like a deep copy. In fact, it is a shallow copy. The elements of the original array will be copied according to the following rules:

var a = [ 1, 3, 5, { x: 1 } ];
var b = Array.prototype.slice.call(a);
b[0] = 2;
console.log(a);//[ 1, 3, 5, { x: 1 } ];
console.log(b);//[ 2, 3, 5, { x: 1 } ];
 

It can be seen from the output result that after the shallow copy, the array a[0] does not change with the change of b[0], indicating that the reference addresses of a and b in the stack memory are not the same.

var a = [ 1, 3, 5, { x: 1 } ];
var b = Array.prototype.slice.call(a);
b[3].x = 2;
console.log(a);//[ 1, 3, 5, { x: 2 } ];
console.log(b);//[ 1, 3, 5, { x: 2 } ];
 

It can be seen from the output result that after shallow copying, the attributes of the objects in the array will change according to the modification, indicating that the attribute references of the objects of the existing objects copied during the shallow copying

...Spread operator

  var cloneObj = { ...obj };
 
let obj = {a:1,b:{c:1}}
let obj2 = {...obj};
obj.a=2;
console.log(obj);//{a:2,b:{c:1}}
console.log(obj2);//{a:1,b:{c:1}}

obj.b.c = 2;
console.log(obj);//{a:2,b:{c:2}}
console.log(obj2);//{a:1,b:{c:2}}
 

The spread operator is also a shallow copy, and the property whose value is an object cannot be completely copied into two different objects

Implement simple reference copy

function shallowClone(copyObj) {
  var obj = {};
  for ( var i in copyObj) {
    obj[i] = copyObj[i];
  }
  return obj;
}
var x = {
  a: 1,
  b: { f: { g: 1 } },
  c: [ 1, 2, 3 ]
};
var y = shallowClone(x);
console.log(y.b.f === x.b.f);    //true
 

Object.assign()

The object.assign() method can copy the enumerable properties of any number of source objects to the target object, and then return the target object.

var obj1 = {
    title: ' ',
    subset: {
        name: ' ',
        subpoll: {
            des: ' '
        },
        sex: function() {
            console.log('sex')
        }
    },
    age: function() {
        console.log('age')
    }
}

//Object.assign() {} 
var ojb2 = Object.assign({}, obj1)
ojb2.title = '1111'//obj1   
ojb2.subset.name = '2222'//obj1   
ojb2.subset.subpoll.des = '3333'//obj1   

// 
console.log('obj1 ', obj1)
console.log('ojb2 ', ojb2)
 

It can be seen from the printing result that it Object.assignis a shallow copy, it just creates a new object in the root attribute (the first level of the object), but if the value of the attribute is an object, it will only copy the same memory address. Object.assign notes 1. Only copy the own properties of the source object (do not copy inherited properties) 2. It will not copy the non-enumerable properties of the object 3. undefinedAnd nullcannot be converted into an object, they cannot be used as Object.assignparameters, but can be used as the source object

Object.assign(undefined)// 
Object.assign(null)// 

let obj = {a: 1};
Object.assign(obj, undefined) === obj//true
Object.assign(obj, null) === obj//true
 

4. The attribute Symbolwhose attribute name is value can be copied by Object.assign

Deep copy

JSON.parse(JSON.stringify())

JSON.stringify() is a commonly used deep copy method in the front-end development process. The principle is to serialize an object into a JSON string, convert the content of the object into a string and save it on the disk, and then use JSON.parse() to deserialize the JSON string into a new object

let arr = [1, 3, {
    username: ' '
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'www.vipbic.com'; 
console.log(arr4);//[ 1, 3, { username: 'www.vipbic.com' } ]
console.log(arr);//[ 1, 3, { username: '  ' } ]
 

A deep copy is implemented. When the value of the object in the array is changed, the content in the original array does not change. Although JSON.stringify() can implement deep copying, it has some drawbacks such as not being able to handle functions.

JSON.stringify() realizes deep copy attention points

1. If there is a function, undefined, and symbol in the value of the
copied object , the key-value pair in the JSON string serialized by JSON.stringify() will disappear . 2. Unenumerable properties cannot be copied, and objects cannot be copied. Prototype chain
3. Copy Date reference type will become a string
4. Copy RegExp reference type will become empty object
5. Object contains NaN, Infinity and -Infinity, the result of serialization will become null
6. Object cannot be copied The cyclic application (ie obj[key] = obj)
summarizes: it will abandon the constructor of the object. After deep copying, no matter what the original constructor of the object is, it will become Object after deep copying; this method can be handled correctly The only objects are Number, String, Boolean, Array, and flat objects. That is to say, only objects that can be converted into JSON format can be used in this way, like functions cannot be converted into JSON;

Manually implement deep copy

This is a chestnut on github, github.com/wengjq/Blog...

//
(function($) {
    "use strict";
    var types = "Array,Object,String,Date,RegExp,Function,Boolean,Number,Null,Undefined".split(",");
    for (let i = types.length; i--; ) {
        $["is" + types[i]] = str => Object.prototype.toString.call(str).slice(8, -1) === types[i];
    }
    return $;
})(window.$ || (window.$ = {})); 

function copy(obj, deep = false, hash = new WeakMap()) {
    if (hash.has(obj)) {
        return hash.get(obj);
    }
    if ($.isFunction(obj)) {
        return new Function("return " + obj.toString())();
    } else if (obj === null || typeof obj !== "object") {
        return obj;
    } else {
        var name,
        target = $.isArray(obj) ? [] : {},
        value;
        hash.set(obj, target);
        for (name in obj) {
            value = obj[name];
            if (deep) {
                if ($.isArray(value) || $.isObject(value)) {
                    target[name] = copy(value, deep, hash);
                } else if ($.isFunction(value)) {
                    target[name] = new Function("return " + value.toString())();
                } else {
                    target[name] = value;
                }
            } else {
                target[name] = value;
            }
        }
      return target;
    }
}

//
var x = {};
var y = {};
x.i = y;
y.i = x;
var z = copy(x, true);
console.log(x, z);

var a = {};
a.a = a;
var b = copy(a, true);
console.log(a, b);
 

Third-party deep copy library

lodash

The function library also provides _.cloneDeep for deep copy (lodash is a good third-party open source library, there are many good functions, you can also see the specific implementation source code)

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);//false
 
jQuery

jquery provides a syntax that $.extendcan be used to do deep copy
: $.extend([deep], target, object1[, objectN])
**deep:** indicates whether the deep copy is false by default. If true is true for deep copy, if it is false, then For shallow copy
target : Object type target object, the member attributes of other objects will be attached to the object.
object1 objectN : The first and Nth merged object of the optional Object type.

var $ = require('jquery');
var obj1 = {
   a: 1,
   b: {
     f: {
       g: 1
     }
   },
   c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);  //false
 

summary

Reference article


The realization of the shallow copy and the deep copy in the Object type javaScript in JavaScript The realization of the
deep copy and the shallow copy

about me