Recently, writing business code under the VUE framework inevitably involves the problem of object depth copy, take the opportunity to summarize and record.

Since the wechat article platform can only be edited again, I will update the article to my personal blog if it is updated in the future. If you are interested, please take a look at my personal blog address: blog.ironmaxi.com

The heap and stack areas of memory

We’ll start with the familiar term “stack” and distinguish between data structures and the memory definition of “stack”.

The heap and stack in a data structure are two different data structures in which the data items are arranged in order.

We will focus on the heap and stack areas of memory.

In C language, the stack area allocates local variable space, while the heap area is used to allocate the memory space applied by the program, and the static area is used to allocate static variables, global variable space. Read-only areas are used to allocate constants and program code space. Here’s a simple example:

int a = 0; // global initializer char *p1; // Global uninitialized areamain() { int b; // stack char s[] ="abc"; / / stack char * (p2); // stack char *p3 ="123456"; // in constant area, p3 is on stack. Static int c =0; // global (static) initializer p1 = (char *)malloc(10); // heap p2 = (char *)malloc(20); / / heap}Copy the code

As a high-level language, JavaScript still relies on C/C++ to compile and implement the underlying language, and its variables are divided into basic data types and reference types. Basic data types include:

  • undefined
  • null
  • boolean
  • number
  • string

Each of these types occupies a fixed amount of space in memory, and their values are kept in stack space, accessed, copied, and compared by value.

Reference types include:

  • object
  • array
  • function
  • error
  • date

These types of values are of variable size, and the stack memory holds addresses that point to objects in the heap memory, which are accessed by reference, just like Pointers in C language.

For reference type variables, the stack memory holds the knowledge of the object’s access address, and the heap memory allocates space for this value. Since the size of this value is not fixed, it cannot be stored in the stack memory. But the memory address size is fixed, so the heap memory address can be stored in stack memory. In this way, when a variable of reference type is queried, the heap memory address is read from the stack and then the corresponding value is fetched based on that address.

One obvious point is that all instances of reference types in JavaScript are explicitly or implicitly new to the corresponding type, which is actually the corresponding malloc memory allocation function.

The assignment of variables in JavaScript

Js variable assignment is divided into “value” and “address”.

Assigning a value of a primitive data type to a variable is “passing”; Assigning a value to a variable that refers to a data type is actually “addressing”.

The assignment and comparison of primitive data type variables is simply the assignment and comparison of values, i.e. the copying and comparison of data in stack memory, as shown in the following intuitive code:

var num1 = 123;
var num2 = 123;
var num3 = num1;
num1 === num2; // true
num1 === num3; // true
num1 = 456;
num1 === num2; // false
num1 === num3; // false
Copy the code

Reference data type variable assignment, comparison, just stored in stack memory heap memory address copy, comparison, participate in the following intuitive code:

var arr1 = [1.2.3];
var arr2 = [1.2.3];
var arr3 = arr1;
arr1 === arr2; // false
arr1 === arr3; // true
arr1 = [1.2.3];
arr1 === arr2; // false
arr1 === arr3; // false
Copy the code

One more point: All top-level prototypes in JS that reference data types are objects, and therefore objects.

Copy of a variable in JavaScript

Copy in JS is classified into “shallow copy” and “deep copy”.

Shallow copy

Shallow copy copies only the properties of the object sequentially, not recursively, that is, only the first-level properties of the target object are assigned.

For the data of the basic data type in the first layer of the target object, it is directly assigned, that is, “pass value”; For the first layer of the target object that refers to the data type, it is the heap memory address directly stored in the stack memory, that is, “address”.

Deep copy

Deep copy differs from shallow copy in that it copies not only the first layer properties of the target object, but all properties of the target object recursively.

In general, when you think about deep copying of compound types in JavaScript, you’re referring to the processing of Date, Object, and Array compound types. The most common approach we can think of is to create an empty new object and then recursively iterate through the old object until the child nodes of the underlying type are found and the new object is assigned its position.

The problem with this approach, however, is that JavaScript has a magic prototype mechanism, and the prototype will appear during traversal, and then you need to consider whether the prototype should be assigned to a new object. During the traversal process, we can consider using the hasOwnProperty method to determine whether to filter out attributes inherited from the prototype chain.

Start to implement a shallow copy plus extension function

function _isPlainObject(target) {
  return (typeof target === 'object'&&!!!!! target && !Array.isArray(target));
}
function shallowExtend() {
  var args = Array.prototype.slice.call(arguments);
  // The first argument is target
  var target = args[0];
  var src;

  target = _isPlainObject(target) ? target : {};
  for (var i=1; i<args.length; i++) { src = args[i];if(! _isPlainObject(src)) {continue;
    }
    for(var key in src) {
      if (src.hasOwnProperty(key)) {
        if(src[key] ! =undefined) { target[key] = src[key]; }}}}return target;
}
Copy the code

Test cases:

// Initialize the reference datatype variable
var target = {
  key: 'value'.num: 1.bool: false.arr: [1.2.3].obj: {
    objKey: 'objValue'}};// Copy + extend
var result = shallowExtend({}, target, {
  key: 'valueChanged'.num: 2.bool: true});// Modify the original reference type data
target.arr.push(4);
target.obj['objKey2'] = 'objValue2';
// Compare attribute values of base data types
result === target; // false
result.key === target.key;  // false
result.num === target.num;  // false
result.bool === target.bool;// false
// Compare attribute values of reference data types
result.arr === target.arr;  // true
result.obj === target.obj;  // true
Copy the code

Jquery. extend extends depth copy function

Extend (jquery.extend) :


jQuery.extend = jQuery.fn.extend = function() {
  var options,
    name,
    src,
    copy,
    copyIsArray,
    clone,
    target = arguments[0] || {},
    i = 1,
    length = arguments.length,
    deep = false;

  // If the first argument is a Boolean, it is a flag variable to determine whether the deep copy is possible
  if (typeof target === "boolean") {
    deep = target;
    // Skip the deep flag variable, noting that the initial value of I is 1 above
    target = arguments[i] || {};
    // I increases by 1
    i++;
  }

  // Check whether target is a type variable other than object/array/function
  if (typeoftarget ! = ="object" && !isFunction(target)) {
    // Force reassignment to a new empty object if it is a variable of another type
    target = {};
  }

  // If only one argument is passed in; Or you can pass in two arguments, the first of which is the deep variable and the second target
  // So the value of length may be 1 or 2, but either way, the next for loop will run only once
  if (i === length) {
    // Assign jQuery itself to target
    target = this;
    // I decrement by 1, possible value is 0 or 1
    i--;
  }

  for (; i < length; i++) {
    // Only arguments[I] that are not null or undefined can be copied
    if ((options = arguments[i]) ! =null) {
      // Extend the base object
      for (name in options) {
        src = target[name];
        copy = options[name];
        // Avoid dead-loop situations
        if (target === copy) {
          continue;
        }
        // Recurse if we're merging plain objects or arrays
        // If it is a deep copy, the copy value is valid, and the copy value is pure Object or array
        if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
          if (copyIsArray) {
            // Array case
            copyIsArray = false;
            clone = src && Array.isArray(src)
              ? src
              : [];
          } else {
            // Object condition
            clone = src && jQuery.isPlainObject(src)
              ? src
              : {};
          }
          // Clone the copy object to the original object and assign the property back instead of reassigning
          // recursive call
          target[name] = jQuery.extend(deep, clone, copy);

          // Don't bring in undefined values
        } else if(copy ! = =undefined) { target[name] = copy; }}}}// Return the modified object
  return target;
};
Copy the code

This method extends an object with one or more other objects and returns the extended object.

If target is not specified, the jQuery namespace itself is extended. This helps plug-in authors add new methods to jQuery.

If the first parameter is set to true, jQuery returns a deep copy, recursively copying whatever object is found. Otherwise, the copy shares the structure with the original object. Undefined properties will not be copied, whereas properties inherited from the object’s prototype will be copied.

ES6 implements shallow copy

Object.assign

The object. assign method copies any number of source objects’ own enumerable attributes to the target Object and returns the target Object.

Note:

  1. For accessor properties, the method will execute that accessor propertygetterIf you want to copy the accessor property itself, use theObject.getOwnPropertyDescriptor()Object.defineProperties()Methods;
  2. Properties of type string and symbol are copied;
  3. An exception may be raised during property copying, such as a read-only property of the target object having the same name as a property of the source objectTypeErrorException: The copy process is interrupted. Properties that have been copied successfully are not affected, and properties that have not been copied will not be copied again.
  4. This method skips those values ofnullundefinedSource object of;

Using JSON for deep copies that ignore prototype chains

var dest = JSON.parse(JSON.stringify(target));
Copy the code

It also has a disadvantage: this method ignores properties with undefined and function expressions, but not properties with null.

Again, the prototype chain attribute

In project practice, it has been found that there are at least two ways to avoid copying on prototype chain attributes.

Method 1

The most common way:

for (let key in targetObj) {
  if (targetObj.hasOwnProperty(key)) {
    // Related operations}}Copy the code

Disadvantages: Traversed all the attributes on the prototype chain, low efficiency;

Way 2

Here’s how the ES6 works:

const keys = Object.keys(targetObj);
keys.map((key) = >{
  // Related operations
});
Copy the code

Note: Only an array of the key names of all of the Enumerable properties of the parameter object itself (not inherited) is returned.

Methods 3

Break new ground:

const obj = Object.create(null);
target.__proto__ = Object.create(null);
for (let key in target) {
  // Related operations
}
Copy the code