background

Leader last week to look at the school recruit students a pen questions and topic is to achieve a deep copy of the function, implementation vary, but most still only a shallow copy, just hired as a small school recruit students, also don’t have much profound technical precipitation, take this small point for a summary, After all, the most awesome technology still needs a solid foundation to understand and apply.

Standard engineering students, not good writing, what wrong place still need big men pointed out.

The data type

Why make a light copy? What data types have shallow and deep copy problems? It starts with data types. In JS, the types of variables are mainly divided into basic data types (string, number, Boolean, NULL, undefined) and reference data types (such as Array, Date, RegExp, Function). The basic types are stored in stack memory. They are simple data segments of fixed size, whereas reference types may be objects made up of multiple values of variable size and stored in heap memory.

Basic types which are accessed by value, can be stored in the variable operation in the actual value, the value of a reference type is stored in memory object, js cannot directly manipulate direct access to the location in memory, also is cannot directly manipulate the object’s memory space, the operation object, the operation is actually object reference, so that the value of a reference type is accessible by reference.

When assigning a value from one variable to a reference type of another variable, the copied copy is actually a pointer to the same object stored in the heap, so changing one variable affects the other.

   var person_1 = {

  1.   name: 'max'.

  2.   age: 22

   };

   var person_2 = person_1;

  1. person_2.name = 'caesar';

  2. console.log(person_1. person_2);    //{name: "caesar", age: 22}

   //{name: "caesar", age: 22}

Copy the code

In this case, we simply copy the address of the reference type, because both reference the same address, and the new variable will change the previous variable, but we don’t want to do that.

Good! That’s all we need. Now we’re moving on, so there’s no problem with depth copying of basic types, and the types we want to copy are definitely reference types, such as Array and Object.

Shallow copy

Well, at this point I wanted to copy an array, and my mind quickly flashed a few native array copy methods, as well as a loop assignment method that I quickly wrote down.

  • Array.prototype.slice

  • Array.prototype.concat

  • ES6 copyWithin()

  • function copyArr(arr) { let res = [] for (let i = 0; i < arr.length; i++) { res.push(arr[i]) } return res }

But are they deep copies? I gave it a try.

I only changed one property of the object in the new array, why did the object in the previous array also change!!

To see why, I need to look at the rationale for implementing them, so I searched the V8 source code (I won’t go into the details of the other features – let’s just look at the key ones), for example: array.prototype. slice:

ArraySliceFallback main function


      
  1. function ArraySliceFallback(start. end) {

  2.  CHECK_OBJECT_COERCIBLE(this. "Array.prototype.slice");

  3.  .

  4.    // Finally returns the value of the result, from which you can see that a new real array has been created

  5.  var result = ArraySpeciesCreate(array. MaxSimple(end_i - start_i. 0));

  6.  if (end_i < start_i) return result;

  7.    // The size of the array is relatively large, but the number of copies is relatively small compared to the total size of the array, but this is irrelevant for our purposes.

  8.  if (UseSparseVariant(array. len. IS_ARRAY(array), end_i - start_i)) {

  9.    %NormalizeElements(array);

  10.    if (IS_ARRAY(result)) %NormalizeElements(result);

  11.    // Look here. Look here

  12.    SparseSlice(array. start_i. end_i - start_i. len. result);

  13.  } else {

  14.      // And here and here

  15.    SimpleSlice(array. start_i. end_i - start_i. len. result);

  16.  }

  17.  result.length = end_i - start_i;

  18.  return result;

  19. }

Copy the code

The main function of the Slice method. I’m going to ignore some of the judgment processing for start and end, and focus on the two branches dealing with SparseSlice and SimpleSlice

SparseSlice


      
  1. function SparseSlice(array. start_i. del_count. len. deleted_elements) {

  2.  var indices = %GetArrayKeys(array. start_i + del_count);

  3.  if (IS_NUMBER(indices)) {

  4.    var limit = indices;

  5.    / / loop

  6.    for (var i = start_i; i < limit; ++i) {

  7.      var current = array[i];

  8.    / / assignment

  9.      if (!IS_UNDEFINED(current) || i in array) {

  10.        %CreateDataProperty(deleted_elements. i - start_i. current);

  11.      }

  12.    }

  13.  } else {

  14.    var length = indices.length;

  15.    / / loop

  16.    for (var k = 0; k < length; ++k) {

  17.      var key = indices[k];

  18.      if (key > = start_i) {

  19.        var current = array[key];

  20.        / / assignment

  21.        if (!IS_UNDEFINED(current) || key in array) {

  22.          %CreateDataProperty(deleted_elements. key - start_i. current);

  23.        }

  24.      }

  25.    }

  26.  }

  27. }

Copy the code

You can see that both branches of the function just loop through the array and then perform direct assignment.

SimpleSlice


      
  1. function SimpleSlice(array. start_i. del_count. len. deleted_elements) {

  2.    // Direct loop

  3.  for (var i = 0; i < del_count; i+ +) {

  4.    var index = start_i + i;

  5.    if (index in array) {

  6.    / / assignment

  7.      var current = array[index];

  8.      %CreateDataProperty(deleted_elements. i. current);

  9.    }

  10.  }

  11. }

Copy the code

The slice method does not make a distinction between a primitive type and a reference type for each item in the loop, and determines the internal attributes of the reference element, so array.prototype. slice is a shallow copy. Behind the two primary methods did not take out to see, is also similar, interest and does not believe a friend can try click this link (https://github.com/v8/v8/blob/master/src/js/array.js)

summary

We can copy the base type directly with =. When we copy the reference type, we loop over the object and use = to complete the copy for each property, so all of the above copies are just copies of the first level property. This is a shallow copy, although we do get a new object independent of the source. But it still contains reference property values that point to the same address (that is, we just copied the address).

What if one of the properties of an object held in an array is still an object? (And then layer by layer? Certainly no one designed such a deep data structure), but to solve this problem, there is a deep copy implementation: the value of all reference types in the property is iterated up to the base type, and to achieve deep copy, you can also think of recursion.

Deep copy

Therefore, deep copy does not simply copy the reference, but reallocate the memory in the heap, and create a new copy of all the attributes of the source object instance to ensure that the reference of the copied object does not point to any object on the original object or its attributes, and the copied object is completely isolated from the original object.

Deep copy implementation of other class libraries (jquery)

Let’s take a look at how jquery implements deep copy. Jquery uses the extend function to make deep copies of objects.


      
  1. jQuery.extend = jQuery.fn.extend = function(a) {

  2.    .

  3.    // We can copy the core code directly

  4.    for ( ; i < length; i++ ) {

  5.       // Only non-null arguments are processed

  6.       if ( (options = arguments[ i ]) ! = null ) {

  7.           for ( name in options ) { // Iterate over the attribute names of the source object

  8.              src = target[ name ]; // Get the property corresponding to the property name on the target object

  9.              copy = options[ name ]; // Get the attribute corresponding to the attribute name on the source object

  10.              // Jq does a better job. To avoid a deep loop, jQ does not overwrite the property of the target object with the same name.

  11.              if ( target = = = copy ) {

  12.                  continue;

  13.              }

  14.              // Deep copy and the values are normal images or arrays, then recursive, that is, jquery makes a recursive deep copy of the objects and arrays created by {} or new Object

  15.              if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {

  16.                  // Copy is an array

  17.                  if ( copyIsArray ) {

  18.                     copyIsArray = false;

  19.                     // clone is the modified value of SRC

  20.                     clone = src && jQuery.isArray(src) ? src : [];

  21.                  // If copy is an object

  22.                  } else {

  23.                     // clone is the modified value of SRC

  24.                     clone = src && jQuery.isPlainObject(src) ? src : {};

  25.                  }

  26.                  // Call jquery.extend recursively

  27.                  target[ name ] = jQuery.extend( deep. clone. copy );

  28.              // Otherwise, if it is not a pure object or array, jquery will eventually assign the properties of the source object directly to the source object (shallow copy).

  29.              } else if ( copy ! = = undefined ) {

  30.                  target[ name ] = copy;

  31.              }

  32.           }

  33.       }

  34.    }

  35.    // Returns the changed object

  36.    return target;

  37. };

Copy the code

But jquery’s deep copy does not copy function objects. See the code below.

We wrote a class function, although it is a function, but it is also a function object and is a reference type, but the jquery does not object to a function make deep processing, it will come to the final elseif inside, the source directly to the object’s properties assigned to the object, that is, or shallow copy, but the function is more used to complete certain functions, There are input values and return values, so it is not necessary to make a deep copy of the function object.

JSON global object

An easy way to copy JSON data objects is to use JSON’s parse and Stringify global objects for deep replication.


      
  1.    function jsonClone(obj) {

  2.        return JSON.parse(JSON.stringify(obj));

  3.    }

Copy the code

But it only works with Number, String, Boolean, Aarry, flat objects, that is, data structures that can be directly represented by JSON, as follows.

A better way to implement deep copy

Originally wanted to write a deep copy, tried to write, after seeing the former people write feeling how much will have the feeling of plagiarism, and the details are not thoughtful (in fact, their level is not enough), or to share with you a predecessor I think write a better deep copy


      
  1. // Define a helper function to define methods on the Prototype of the predefined object:

  2. function defineMethods(protoArray. nameToFunc) {

  3.    protoArray.forEach(function(proto) {

  4.        var names = Object.keys(nameToFunc),

  5.            i = 0;

  6.        for (; i < names.length; i+ +) {

  7.            Object.defineProperty(proto. names[i]. {

  8.                enumerable: false.

  9.                configurable: true.

  10.                writable: true.

  11.                value: nameToFunc[names[i]]

  12.            });

  13.        }

  14.    });

  15. }

  16. //Object processing

  17. defineMethods([ Object.prototype ]. {

  18. SrcStack and dstStack. Their main use is to make deep copies of objects where loops exist. For example, the child object srcStack[7] in the source object corresponds to dstStack after deep copy. [7]

  19.    '$clone': function (srcStack. dstStack) {

  20.        var obj = Object.create(Object.getPrototypeOf(this)),

  21.            keys = Object.keys(this),

  22.            index.

  23.            prop;

  24.        srcStack = srcStack || [];

  25.        dstStack = dstStack || [];

  26.        srcStack.push(this);

  27.        dstStack.push(obj);

  28.        for (var i = 0; i < keys.length; i+ +) {

  29.            prop = this[keys[i]].

  30.            if (prop = = = null || prop = = = undefined) {

  31.                obj[keys[i]] = prop;

  32.            }

  33.            else if (!prop.$isFunction()) {

  34.                if (prop.$isPlainObject()) {

  35.                    index = srcStack.lastIndexOf(prop);

  36.                    if (index > 0) {

  37.                        obj[keys[i]] = dstStack[index];

  38.                        continue;

  39.                    }

  40.                }

  41.                obj[keys[i]] = prop.$clone(srcStack. dstStack);

  42.            }

  43.        }

  44.        return obj;

  45.    }

  46. });

  47. / / Array processing

  48. defineMethods([ Array.prototype ]. {

  49.    '$clone': function (srcStack. dstStack) {

  50.        var thisArr = this.valueOf(),

  51.            newArr = [].

  52.            keys = Object.keys(thisArr),

  53.            index.

  54.            element;

  55.        srcStack = srcStack || [];

  56.        dstStack = dstStack || [];

  57.        srcStack.push(this);

  58.        dstStack.push(newArr);

  59.        for (var i = 0; i < keys.length; i+ +) {

  60.            element = thisArr[keys[i]].

  61.            if (element = = = undefined || element = = = null) {

  62.                newArr[keys[i]] = element;

  63.            } else if (!element.$isFunction()) {

  64.                if (element.$isPlainObject()) {

  65.                    index = srcStack.lastIndexOf(element);

  66.                    if (index > 0) {

  67.                        newArr[keys[i]] = dstStack[index];

  68.                        continue;

  69.                    }

  70.                }

  71.            }

  72.            newArr[keys[i]] = element.$clone(srcStack. dstStack);

  73.        }

  74.        return newArr;

  75.    }

  76. });

  77. // Processing of type Date

  78. defineMethods([ Date.prototype ]. {

  79.    '$clone': function(a) { return new Date(this.valueOf()); }

  80. });

  81. / / the RegExp processing

  82. defineMethods([ RegExp.prototype ]. {

  83.    '$clone': function (a) {

  84.        var pattern = this.valueOf(a);

  85.        var flags = ' ';

  86.        flags + = pattern.global ? 'g' : ' ';

  87.        flags + = pattern.ignoreCase ? 'i' : ' ';

  88.        flags + = pattern.multiline ? 'm' : ' ';

  89.        return new RegExp(pattern.source. flags);

  90.    }

  91. });

  92. // Handle Number, Boolean, and String. This prevents objects like a single String from incorrectly calling Object.prototype.$clone

  93. defineMethods([

  94.    Number.prototype.

  95.    Boolean.prototype.

  96.    String.prototype

  97. ]. {

  98.    '$clone': function(a) { return this.valueOf(a); }

  99. });

Copy the code

We can not only implement a deep copy of Object and Array, but also consider the two reference types of Date RegExp. We still need to work hard on this front end. We need to work hard on this front end.

conclusion

Not all variable assignments require a deep copy, and in most cases we only need a shallow copy, as jquery adds the first argument for developers to choose if a deep copy is required.

The first time to write an article, a lot of places are not in place, but finally feel the harvest is quite abundant, in the depth of the copy has a deeper impression and understanding.

— — — — — — — — —

Long press the QR code to follow the big Zhuan FE