[Through Js] In-depth study of shallow copy and deep copy

To really understand deep and shallow copying, you must master the basics of assignment, storage of objects in memory, and data types.

In order to better grasp the deep and shallow copy, let's first look at the data type and the storage form in memory.

1. JavaScript data types

primitive type

  • Null: Contains only one value:null
  • Undefined: Contains only one value:undefined
  • Boolean: Contains two values: trueandfalse
  • Number: Integer or float, and some special values ​​( -Infinity, +Infinity, NaN)
  • String: A sequence of characters representing a text value
  • Symbol: A data type whose instance is unique and immutable

object type

  • Object: It is not too much to classify by yourself, except for the commonly used Object, Array, Function, etc. are all special objects

2. The difference between primitive types and object types

1. Primitive types

In JavaScript, every variable requires a memory space to store.
The memory space is divided into two types: heap memory and stack memory.

The value of the primitive type in JavaScript is directly stored in the stack, and when the variable is defined, the stack allocates memory space for it.

Stack memory features:

  • Stored values ​​are of fixed size
  • less space
  • It can directly operate the saved variables, and the operation efficiency is high
  • The storage space is automatically allocated by the system

The characteristic of primitive types is immutability, that is, the value itself cannot be changed.

var str = 'JingYu';
str.slice(1);
str.substr(1);
str.trim(1);
str.toLowerCase(1);
str[0] = 1;
console.log(str);  // JingYu

There is a special case:

str += '6'
console.log(str);  // JingYu6

Is this situation not satisfying immutability? No, in the above code, we have performed the operation of str += '6', in fact, another memory space has been opened up in the stack for storing 'JingYu6', and then the variable str points to this space, so This does not violate the characteristics of immutability.
insert image description here

2. Reference type

The value of the reference type (object type) in JavaScript is actually directly stored in the heap memory, and only a fixed-length address is stored in the stack memory, which points to the value in the heap memory.

Heap memory features:

  • The stored value is variable in size and can be adjusted dynamically
  • Large space, low operating efficiency
  • Cannot directly operate its internal storage, use reference address to read
  • Allocate space through code

The reference type does not have the characteristics of immutability, we can easily change it, especially the array has many functions to change:

  • pop()Delete the last element of the array, if the array is empty, do not change the array, return undefined, change the original array, return the deleted element
  • push()Add one or more elements to the end of the array, change the original array, and return the length of the new array
  • shift()Delete the first element of the array, if the array is empty, do not perform any operation, return undefined, change the original array, and return the value of the first element
  • unshift()Adds one or more elements to the beginning of an array, alters the original array, and returns the length of the new array
  • reverse()Reverse the order of the elements in the array, change the original array, and return the array
  • sort()Sort the array elements, change the original array, and return the array
  • splice()Add/remove items from an array, mutate the original array, and return the removed element

3. copy

When we copy a variable to another variable, primitive types and reference types behave differently.
Primitive type:

var name = 'JingYu';
var name2 = name;
name2 = 'JINGYU';
console.log(name); // JingYu;

It can be seen from the output results that when we modify the result of name2, it has no effect on name. That's because when we namecopy the variable to name2, we create a new memory space in the stack memory space. This memory space stores the name2value of the variable. The value nameis the same as the variable but the address of the memory space is completely different. So the modified name2value nameremains unchanged.
Reference type:

var obj = {
    
    name:'JingYu'};
var obj2 = obj;
obj2.name = 'JINGYU';
console.log(obj.name); // JINGYU

You will be surprised to find that the displayed result is different from the original type. This is why?
That's because when we copy a variable of reference type, we actually copy the address stored in the stack, so the copied obj2 is actually the same object in the heap pointed to by obj. Therefore, if we change the value of any one of the variables, the other variable will be affected, which is why there are deep and shallow copies.

4. Compare

Look at the code first, guess the running result...

var name = 'JingYu';
var name2 = 'JingYu';
console.log(name === name2); // true
var obj = {
    
    name:'JingYu'};
var obj2 = {
    
    name:'JingYu'};
console.log(obj === obj2); // false

For primitive types, their values ​​are directly compared when comparing, and true is returned if the values ​​are equal.
For reference types, their reference addresses will be compared during comparison. Although the attributes of the objects stored in the heap by the two variables are equal, they are stored in different storage spaces, so the comparison value is false.

5. Pass by value

First give a conclusion: The parameters of all functions in ECMAScript are passed by value.
There is no pass-by-reference in ECMAScript.

let name = 'JingYu';
function changeValue(name){
    
    
  name = 'JINGYU';
}
changeValue(name);
console.log(name);

Obviously, the above execution result is 'JingYu', that is, the function parameter is only a local variable copied by the incoming variable, and changing this local variable will not affect the external variable.

let obj = {
    
    name:'JingYu'};
function changeValue(obj){
    
    
  obj.name = 'JINGYU';
}
changeValue(obj);
console.log(obj.name); //JINGYU

When the function parameter is a reference type, we also copy a copy of the parameter to the local variable, but the copied copy points to the address in the heap memory. We operate on the properties of the object inside the function, which is actually different from the outside Variables point to the same value in heap memory, but this does not imply pass-by-reference.

let obj = {
    
    };
function changeValue(obj){
    
    
  obj.name = 'JingYu';
  obj = {
    
    name:'JINGYU'};
}
changeValue(obj);
console.log(obj.name); // JingYu

obj = {name:'JINGYU'};This is just a local object inside the function.

3. Shallow copy

We have introduced the basic knowledge earlier. Now to our topic.

concept

Shallow copy refers to the creation of new data that has an exact copy of the original data's attribute values.
If the attribute is a primitive type, the value of the primitive type is copied. If the attribute is a reference type, the memory address is copied.
That is, a shallow copy is a layer copy, and a deep reference type shares the memory address.

Implementation

simple shallow copy

function shallowClone(obj) {
    
    
    const newObj = {
    
    };
    for(let prop in obj) {
    
    
        if(obj.hasOwnProperty(prop)){
    
    
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
}

In JavaScript, there are three other shallow copy phenomena:
Object.assign

var obj = {
    
    
    age: 18,
    nature: ['smart', 'good'],
    names: {
    
    
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
    
    
        console.log('fx is a great girl')
    }
}
var newObj = Object.assign({
    
    }, fxObj);

Array.prototype.slice(), Array.prototype.concat()

const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]

Copy using the spread operator

const fxArr = ["One", "Two", "Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]

Four, deep copy

concept

Deep copy creates a new stack. The properties of the two objects are the same, but they correspond to two different addresses. Modifying the properties of one object will not change the properties of the other object. Common deep copy methods
:
_.cloneDeep()

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

jQuery.extend()

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

JSON.stringify()

const obj2=JSON.parse(JSON.stringify(obj1));

But this method has disadvantages, it will ignore undefined, symbol and function

const obj = {
    
    
    name: 'A',
    name1: undefined,
    name3: function() {
    
    },
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

handwritten loop recursion

function deepClone(obj, hash = new WeakMap()) {
    
    
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    
    
    if (obj.hasOwnProperty(key)) {
    
    
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

Five, the difference between shallow copy, deep copy and assignment

First of all, with the help of two pictures, you can see the difference between shallow copy and deep copy more clearly

insert image description here
From the above figure, it is found that both shallow copy and deep copy create a new object, but when copying object properties, the behavior is different.
Shallow copy only copies the pointer of the property to an object, not the object itself. Still sharing the same memory, modifying object properties will affect the original object

// 浅拷贝
const obj1 = {
    
    
    name : 'init',
    arr : [1,[2,3],4],
};
const obj3=shallowClone(obj1) // 一个浅拷贝方法
obj3.name = "update";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存

console.log('obj1',obj1) // obj1 { name: 'init',  arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }

But the deep copy will create another exactly the same object, the new object does not share memory with the original object, and modifying the new object will not change the original object

// 深拷贝
const obj1 = {
    
    
    name : 'init',
    arr : [1,[2,3],4],
};
const obj4=deepClone(obj1) // 一个深拷贝方法
obj4.name = "update";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存

console.log('obj1',obj1) // obj1 { name: 'init', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }

shallow copy and assignment

assignment

 var obj1={
    
    
        name:'张三',
        age:18,
        class:['一班']
    }
    var obj2=obj1//进行了赋值的操作
    obj2.name='李四'
    obj2.class[0]='二班'
    console.log(obj1)
    console.log(obj2)

insert image description here
It can be seen from the example that the assigned object obj2 changes, and the value of the original object obj1 also changes. This is because the assigned object obj2 is assigned the stack memory address of the original object obj1 , and they point to the same heap memory data . Therefore, operating on the data of the assigned object obj2 will change the data in the public heap memory, so the value of the original object will also change.
shallow copy

 var obj1={
    
    
        name:'张三',
        age:18,
        class:['一班']
    }
  function qianCopy(obj){
    
    
    var obj2={
    
    }
    for(var attr in obj){
    
    //循环对象的所有属性值
         if(obj.hasOwnProperty(attr)){
    
    
             obj2[attr]=obj1[attr]
         }
    }
    return obj2
  }
  var obj3=qianCopy(obj1)
  obj3.name='李四'
  obj3.age = 20
  obj3.class[0]='二·班'
  console.log(obj1)
  console.log(obj3)

insert image description here
It can be seen from the results that obj3 changed the value name of the basic type, but did not change the name of the original object obj1, and obj3 changed the value of the reference type, causing the value of the original object to change

6. Summary

Assignment is a complete copy. When assigning an object to another object, it just copies the memory address of an object in the stack to another object. If the property value of one object is changed, the property value of the other object will also change.
Shallow copy is to copy one layer. When the attribute is an object, shallow copy is copying. What is copied is the memory address of the object. Two objects point to the same address. Deep copy is a recursive copy deep layer. When the attribute is an object, deep copy is newly
opened Stack, two objects point to different addresses

Guess you like

Origin blog.csdn.net/qq_46285118/article/details/129005786