Analysis of Shallow Copy and Deep Copy in javaScript

  • 2021-07-21 07:07:12
  • OfStack

1. Variable type of javaScript

(1) Basic type:

Five basic data types: Undefined, Null, Boolean, Number and String. Variables are stored directly by value. They are simple data segments stored in stack memory and can be accessed directly.

(2) Reference type:

Objects and variables stored in heap memory hold a pointer that points to another location. When you need to access the value of a reference type (such as an object, an array, etc.), you first get the address pointer of the object from the stack, and then get the required data from the heap memory.

JavaScript storage objects store addresses, so a shallow copy causes obj1 and obj2 to point to the same block of memory address. If one of the contents is changed, the original memory will be modified, which will lead to the change of both the copy object and the source object, while the deep copy is to open up a new memory address and copy each attribute of the original object one by one. The operations on the copy object and the source object do not affect each other.

Example: Array copy


// Shallow copy, bidirectional change , Point to the same 1 Slice memory space 
var arr1 = [1, 2, 3];
var arr2 = arr1;
arr1[0] = 'change';
console.log('shallow copy: ' + arr1 + " ); //shallow copy: change,2,3
console.log('shallow copy: ' + arr2 + " ); //shallow copy: change,2,3

2. Realization of shallow copy

2.1. Simple Reference Replication


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

2.2. Object. assign ()

The Object. assign () method copies any number of the source object's own enumerable properties to the target object and returns the target object. Object. assign skips source objects with values of null or undefined.


var x = {
 a: 1,
 b: { f: { g: 1 } },
 c: [ 1, 2, 3 ]
};
var y = Object.assign({}, x);
console.log(y.b.f === x.b.f);  // true

3. The realization of deep copy

3.1. slice and concat Methods of Array

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

If the element is an object reference (not an actual object), slice copies the object reference to the new array. Both object references refer to the same object. If the referenced object changes, this element in the new and original arrays will also change. For strings, numbers, and Boolean values (not String, Number, or Boolean objects), slice copies these values into a new array. Modifying these strings or numbers or Boolean values in another array will not affect the other array.

If a new element is added to either one of the two arrays, the other one will not be affected. Examples are as follows:


var array = [1,2,3]; 
var array_shallow = array; 
var array_concat = array.concat(); 
var array_slice = array.slice(0); 
console.log(array === array_shallow); //true 
console.log(array === array_slice); //false "Looks" like a deep copy 
console.log(array === array_concat); //false "Looks" like a deep copy 

As you can see, concat and slice return different array instances, which is different from direct reference copying. From another example, it can be seen that concat and slice of Array are not really deep copies, and the object elements in the array (Object, Array, etc.) just copy references. As follows:


var array = [1, [1,2,3], {name:"array"}]; 
var array_concat = array.concat();
var array_slice = array.slice(0);
array_concat[1][0] = 5; // Change array_concat Values of array elements in  
console.log(array[1]); //[5,2,3] 
console.log(array_slice[1]); //[5,2,3] 
array_slice[2].name = "array_slice"; // Change array_slice The value of the object element in the  
console.log(array[2].name); //array_slice
console.log(array_concat[2].name); //array_slice

3.2. parse and stringify for the JSON object

JSON object is a new type introduced in ES5 (supported browser is IE8 +). parse method of JSON object can deserialize JSON string into JS object, stringify method can serialize JS object into JSON string. With these two methods, deep copy of objects can also be realized.


// Example 1
var source = { name:"source", child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
target.name = "target"; // Change target Adj. name Attribute 
console.log(source.name); //source 
console.log(target.name); //target
target.child.name = "target child"; // Change target Adj. child 
console.log(source.child.name); //child 
console.log(target.child.name); //target child
// Example 2
var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
// Example 3
var source = { name:function(){console.log(1);}, child:new RegExp("e") }
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
console.log(target.child); //Object {}

This method is simple to use, can meet the basic deep copy requirements, and can handle all data types that JSON format can represent, but it can't deeply copy regular expression types, function types, etc. (and will directly lose the corresponding values). Another downside is that it discards the object's constructor. That is, after deep copy, whatever the original constructor of this object is, it will become Object after deep copy. At the same time, if there is a circular reference in the object, it cannot be handled correctly.

4. jQuery. extend () method source code implementation

jQuery source code-src/core. js # L121 source code and analysis as follows:


jQuery.extend = jQuery.fn.extend = function() { // To jQuery Object and jQuery Prototype objects have been added extend Extension method 
var options, name, src, copy, copyIsArray, clone,
 target = arguments[0] || {},
 i = 1,
 length = arguments.length,
 deep = false;
 // The variables above: options Yes 1 Cache variables used to cache arguments[i] , name Is used to receive the object to be extended key , src Before the change target Object for each key Corresponding value . 
 //copy Each of the passed-in objects key Corresponding value , copyIsArray Judge copy Whether it is 1 An array of, clone Used to temporarily store objects or arrays in deep copies src . 
//  The case of dealing with deep copies 
if ( typeof target === "boolean" ) {
 deep = target;
 target = arguments[1] || {};
 // Skipping Boolean and Target  
 i++;
}

 
//  Control when target No object Or function The situation of 
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
 target = {};
}

//  When the length of the parameter list is equal to i Expand the jQuery Object itself. 
if ( length === i ) {
 target = this;
 --i;
}

for ( ; i < length; i++ ) {
 if ( (options = arguments[ i ]) != null ) {
  //  Extend the base object 
  for ( name in options ) {
   src = target[ name ];
   copy = options[ name ];

   //  To prevent endless cycles, here is an example, such as var i = {};i.a = i;$.extend(true,{},i); If there is no such judgment, it will become an infinite loop 
   if ( target === copy ) {
    continue;
   }
   if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
    if ( copyIsArray ) {
     copyIsArray = false;
     clone = src && jQuery.isArray(src) ? src : [];//  If src If it exists and is an array, it makes clone Copy is equal to src Otherwise, it is equal to an empty array. 
    } else {
     clone = src && jQuery.isPlainObject(src) ? src : {};//  If src If it exists and is an object, let clone Copy is equal to src Otherwise, it is equal to an empty array. 
    }

    //  Recursive copy 
    target[ name ] = jQuery.extend( deep, clone, copy );

   } else if ( copy !== undefined ) {
    target[ name ] = copy;//  If the original object exists name Attribute, it is directly overwritten; If it does not exist, a new property is created. 
   }
  }
 }
}

//  Returns the modified object 
return target;
};

The extend method of jQuery implements shallow and deep copies using basic recursive ideas, but this method also cannot handle circular references within source objects, such as:


var a={"name":"aaa"};
var b={"name":"bbb"};
a.child=b;
b.parent=a;
$.extend(true,{},a);// Directly reported stack overflow. Uncaught RangeError: Maximum call stack size exceeded

5. Implement a copy method by yourself


var $ = (function(){
 var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');
 function type() {
 return Object.prototype.toString.call(this).slice(8, -1);
 }
 for (var i = types.length; i--;) {
  $['is' + types[i]] = (function (self) {
  return function (elem) {
   return type.call(elem) === self;
  };
 })(types[i]);
 }
 return $;
})();// Type judgment 

function copy(obj,deep){ 
 if(obj === null || typeof obj !== "object"){ 
  return obj; 
 }        
 var name, target = $.isArray(obj) ? [] : {}, value; 
 for(name in obj){ 
  value = obj[name]; 
  if(value === obj) {
  continue;
  }
  if(deep && ($.isArray(value) || $.isObject(value))){
  target[name] = copy(value,deep);
  }else{
  target[name] = value;
  } 
 } 
 return target;
}


Related articles: