[Ththrough Js] Estudio en profundidad de copia superficial y copia profunda

Para comprender realmente la copia profunda y superficial, debe dominar los conceptos básicos de asignación, almacenamiento de objetos en la memoria y tipos de datos.

Para comprender mejor la copia profunda y superficial, primero veamos el tipo de datos y la forma de almacenamiento en la memoria.

1. Tipos de datos de JavaScript

tipo primitivo

  • Null: Contiene un solo valor:null
  • Undefined: Contiene un solo valor:undefined
  • Boolean: Contiene dos valores: trueyfalse
  • Number: Entero o float, y algunos valores especiales ( -Infinity, +Infinity, NaN)
  • String: una secuencia de caracteres que representan un valor de texto
  • Symbol: un tipo de datos cuya instancia es única e inmutable

tipo de objeto

  • Object: No es demasiado para clasificar por sí mismo, a excepción de los objetos de uso común, matriz, función, etc., todos son objetos especiales.

2. La diferencia entre tipos primitivos y tipos de objetos

1. Tipos primitivos

En JavaScript, cada variable requiere un espacio de memoria para almacenar.
El espacio de memoria se divide en dos tipos: memoria de pila y memoria de pila.

El valor del tipo primitivo en JavaScript se almacena directamente en la pila, y cuando se define la variable, la pila le asigna espacio de memoria.

Características de la memoria de pila:

  • Los valores almacenados son de tamaño fijo
  • menos espacio
  • Puede operar directamente las variables guardadas, y la eficiencia de operación es alta
  • El espacio de almacenamiento es asignado automáticamente por el sistema

La característica de los tipos primitivos es la inmutabilidad, es decir, el valor en sí no se puede cambiar.

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

Hay un caso especial:

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

¿Esta situación no satisface la inmutabilidad? No, en el código anterior, hemos realizado la operación de str += '6', de hecho, se ha abierto otro espacio de memoria en la pila para almacenar 'JingYu6', y luego la variable str apunta a este espacio, entonces Esto no viola las características de inmutabilidad.
inserte la descripción de la imagen aquí

2. Tipo de referencia

El valor del tipo de referencia (tipo de objeto) en JavaScript en realidad se almacena directamente en la memoria del montón, y solo se almacena una dirección de longitud fija en la memoria de la pila, que apunta al valor en la memoria del montón.

Características de la memoria del montón:

  • El valor almacenado es de tamaño variable y se puede ajustar dinámicamente
  • Gran espacio, baja eficiencia operativa
  • No se puede manipular directamente su almacenamiento interno, use la dirección de referencia para leer
  • Asignar espacio a través del código

El tipo de referencia no tiene las características de inmutabilidad, podemos cambiarlo fácilmente, especialmente la matriz tiene muchas funciones para cambiar:

  • pop()Elimine el último elemento de la matriz, si la matriz está vacía, no cambie la matriz, devuelva indefinido, cambie la matriz original, devuelva el elemento eliminado
  • push()Agregue uno o más elementos al final de la matriz, cambie la matriz original y devuelva la longitud de la nueva matriz
  • shift()Elimine el primer elemento de la matriz, si la matriz está vacía, no realice ninguna operación, devuelva indefinido, cambie la matriz original y devuelva el valor del primer elemento
  • unshift()Agrega uno o más elementos al comienzo de una matriz, modifica la matriz original y devuelve la longitud de la nueva matriz
  • reverse()Invierta el orden de los elementos en la matriz, cambie la matriz original y devuelva la matriz
  • sort()Ordene los elementos de la matriz, cambie la matriz original y devuelva la matriz
  • splice()Agregar/eliminar elementos de una matriz, mutar la matriz original y devolver el elemento eliminado

3. copiar

Cuando copiamos una variable a otra variable, los tipos primitivos y los tipos de referencia se comportan de manera diferente.
tipo primitivo:

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

Se puede ver a partir de los resultados de salida que cuando modificamos el resultado de name2, no tiene efecto en name. Eso es porque cuando copiamos namela variable a name2, creamos un nuevo espacio de memoria en el espacio de memoria de la pila. Este espacio de memoria almacena el name2valor de la variable. El valor namees el mismo que la variable pero la dirección del espacio de memoria es completamente diferente. Entonces el name2valor modificado namepermanece sin cambios.
Tipo de referencia:

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

Se sorprenderá al descubrir que el resultado que se muestra es diferente del tipo original. ¿Esta es la razón por?
Esto se debe a que cuando copiamos una variable de tipo de referencia, en realidad copiamos la dirección almacenada en la pila, por lo que el obj2 copiado es en realidad el mismo objeto en el montón al que apunta obj. Por tanto, si cambiamos el valor de alguna de las variables, la otra variable se verá afectada, por lo que existen copias profundas y superficiales.

4. Compara

Mire el código primero, adivine el resultado de la ejecución...

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

Para los tipos primitivos, sus valores se comparan directamente al comparar, y se devuelve verdadero si los valores son iguales.
Para los tipos de referencia, sus direcciones de referencia se compararán durante la comparación. Aunque los atributos de los objetos almacenados en el montón por las dos variables son iguales, se almacenan en diferentes espacios de almacenamiento, por lo que el valor de comparación es falso.

5. Pasar por valor

Primero dé una conclusión: los parámetros de todas las funciones en ECMAScript se pasan por valor.
No hay paso por referencia en ECMAScript.

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

Obviamente, el resultado de la ejecución anterior es 'JingYu', es decir, el parámetro de la función es solo una variable local copiada por la variable entrante, y cambiar esta variable local no afectará la variable externa.

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

Cuando el parámetro de función es un tipo de referencia, también copiamos una copia del parámetro a la variable local, pero la copia copiada apunta a la dirección en la memoria del montón. Operamos en las propiedades del objeto dentro de la función, que en realidad es diferentes de las variables externas apuntan al mismo valor en la memoria del montón, pero esto no implica pasar por referencia.

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

obj = {name:'JINGYU'};Este es solo un objeto local dentro de la función.

3. Copia superficial

Hemos introducido los conocimientos básicos anteriormente. Ahora a nuestro tema.

concepto

La copia superficial se refiere a la creación de nuevos datos que tienen una copia exacta de los valores de atributo de los datos originales.
Si el atributo es un tipo primitivo, se copia el valor del tipo primitivo. Si el atributo es un tipo de referencia, se copia la dirección de memoria,
es decir, una copia superficial es una copia de capa y un tipo de referencia profunda comparte la dirección de memoria.

Implementación

copia superficial simple

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

En JavaScript, hay otros tres fenómenos de copia superficial:
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"]

Copia usando el operador de extensión

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

Cuatro, copia profunda

concepto

La copia profunda crea una nueva pila. Las propiedades de los dos objetos son las mismas, pero corresponden a dos direcciones diferentes. La modificación de las propiedades de un objeto no cambiará las propiedades del otro objeto. Métodos comunes de copia profunda
:
_.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));

Pero este método tiene desventajas, ignorará undefined, símbolo y función

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

recursividad de bucle escrito a mano

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;
}

Cinco, la diferencia entre copia superficial, copia profunda y asignación

En primer lugar, con la ayuda de dos imágenes, puede ver más claramente la diferencia entre la copia superficial y la copia profunda.

inserte la descripción de la imagen aquí
De la figura anterior, se encuentra que tanto la copia superficial como la copia profunda crean un nuevo objeto, pero cuando se copian las propiedades del objeto, el comportamiento es diferente. La
copia superficial solo copia el puntero de la propiedad a un objeto, no el objeto en sí. compartiendo la misma memoria, la modificación de las propiedades del objeto afectará al objeto original

// 浅拷贝
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 ] }

Pero la copia profunda creará otro exactamente el mismo objeto, el nuevo objeto no comparte memoria con el objeto original, y la modificación del nuevo objeto no cambiará el objeto original.

// 深拷贝
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 ] }

copia superficial y asignación

asignación

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

inserte la descripción de la imagen aquí
Se puede ver en el ejemplo que el objeto obj2 asignado cambia, y el valor del objeto original obj1 también cambia.Esto se debe a que al objeto obj2 asignado se le asigna la dirección de memoria de pila del objeto obj1 original, y apuntan a la misma datos de la memoria del montón Por lo tanto, operar en los datos del objeto obj2 asignado cambiará los datos en la memoria del montón pública, por lo que el valor del objeto original también cambiará.
copia superficial

 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)

inserte la descripción de la imagen aquí
Se puede ver a partir de los resultados que obj3 cambió el nombre del valor del tipo básico, pero no cambió el nombre del objeto original obj1, y obj3 cambió el valor del tipo de referencia, lo que provocó que el valor del objeto original cambiara

6. Resumen

La asignación es una copia completa. Al asignar un objeto a otro objeto, simplemente copia la dirección de memoria de un objeto en la pila a otro objeto. Si se cambia el valor de la propiedad de un objeto, el valor de la propiedad del otro objeto también cambiará. .
La copia superficial es copiar una capa. Cuando el atributo es un objeto, la copia superficial es copiar. Lo que se copia es la dirección de memoria del objeto. Dos objetos apuntan a la misma dirección. La copia profunda es una capa profunda de copia recursiva. Cuando el atributo es un objeto, la copia profunda es
una pila recién abierta, dos objetos apuntan a direcciones diferentes

Supongo que te gusta

Origin blog.csdn.net/qq_46285118/article/details/129005786
Recomendado
Clasificación