- Conecptos básicos
La copia de objetos consiste fundamentalmente en duplicar un objeto, pero el método utilizado determina el resultado obtenido. Por ejemplo, si asignamos directamente un objeto a una nueva variable:
// 1. Crear un objeto
var usuario = {
nombre: "Juan Pérez",
hobby: "fotografía"
};
// 2. Asignar el objeto a otra variable
var duplicado = usuario;
// 3. Modificar una propiedad del objeto original
usuario.hobby = "montaña";
// 4. Mostrar la variable duplicada
console.log(duplicado);
El resultado demuestra que, aunque modificamos únicamente la propiedad del objeto original, la copia también se ve afectada. Esto ocurre porque, al crear el objeto original, se reserva un espacio en la memoria heap para almacenar su contenido.
Cuando asignamos duplicado = usuario, no se reserva un nuevo espacio en memoria heap. En su lugar, ambas variables contienen la misma dirección de memoria (referencia) que apunta al mismo objeto en la memoria heap. Por lo tanto, ambas variables comparten el mismo contenido en memoria.
Este comportamiento no es el deseado en la mayoría de los casos. Se necesita una copia que genere un objeto completamente independiente, lo cual se logra mediante copias superficiales (shallow copy) y copias profundas (deep copy).
Es importante notar que los tipos de datos primitivos (Number, Boolean, String, undefined, null) se almacenan directamente en la memoria stack, creando una copia independiente al ser asignados, a diferencia de los objetos que solo copian la referencia.
- Copia superficial: principio y métodos comunes
La copia superficial únicamente copia el primer nivel del objeto. Por ejemplo, considerando un objeto con una propiedad simple:
var datos = {
nombre: "María García",
profesion: "desarrolladora"
};
Para copiarlo a una nueva varible, se reserva un nuevo espacio en memoria y se copian las propiedades directamente:
// 1. Crear un objeto
var datos = {
nombre: "María García",
profesion: "desarrolladora"
};
// 2. Función para realizar la copia superficial
function clonarObjeto(objeto) {
let copia = {};
// Recorrer las propiedades del objeto
for (let propiedad in objeto) {
copia[propiedad] = objeto[propiedad];
}
return copia;
}
// 3. Ejecutar la función
var clon = clonarObjeto(datos);
// 4. Modificar propiedad del objeto original
datos.profesion = "ingeniera";
// 5. Mostrar resultado
console.log(clon);
El resultado muestra que el objeto clonado no se ve afectado por los cambios en el objeto original.
Sin embargo, este método presenta limitaciones cuando el objeto contiene propiedades anidadas:
var datos = {
nombre: "María García",
profesion: "desarrolladora",
contacto: {
telefono: "123456789",
email: "correo@ejemplo.com"
}
};
Con objetos anidados, la copia superficial no crea una instancia independiente de las propiedades internas. Las propiedades de tipo objeto o array mantienen la misma referencia en memoria. Esto ocurre porque el ciclo solo itera sobre el primer nivel del objeto.
Para lograr una independencia completa del objeto, se requiere una copia profunda, que se detalla más adelante. La copia superficial es apropiada cuando el objeto solo contiene propiedades de primer nivel.
Métodos comunes para copia superficial:
Método 1: Utilizando for...in
function clonarObjeto(objeto) {
let copia = {};
for (let propiedad in objeto) {
copia[propiedad] = objeto[propiedad];
}
return copia;
}
Método 2: Utilizando Object.keys()
El método Object.keys() devuelve un array con las propiedades enumerables de un objeto.
function clonarObjeto(objeto) {
let copia = {};
for (let propiedad of Object.keys(objeto)) {
copia[propiedad] = objeto[propiedad];
}
return copia;
}
Método 3: Utilizando Object.entries()
Object.entries() devuelve un array de pares [clave, valor] de las propiedades enumerables.
function clonarObjeto(objeto) {
let copia = {};
for (let [clave, valor] of Object.entries(objeto)) {
copia[clave] = valor;
}
return copia;
}
Método 4: Utilizando Object.getOwnPropertyNames() con forEach
Object.getOwnPropertyNames() devuelve un array con todas las propiedades del objeto.
function clonarObjeto(objeto) {
let copia = {};
Object.getOwnPropertyNames(objeto).forEach(function(propiedad) {
copia[propiedad] = objeto[propiedad];
});
return copia;
}
Método 5: Utilizando Object.defineProperty()
Object.defineProperty() define o modifica una propiedad directamente en un objeto. Se utiliza conjuntamente con Object.getOwnPropertyDescriptor() para obtener el desrciptor de propiedad original.
function clonarObjeto(objeto) {
let copia = {};
Object.getOwnPropertyNames(objeto).forEach(function(propiedad) {
var descriptor = Object.getOwnPropertyDescriptor(objeto, propiedad);
Object.defineProperty(copia, propiedad, descriptor);
});
return copia;
}
Existen otros métodos adicionales que no se incluyen en esta explicación.
- Métodos comunes para copia profunda
A diferencia de la copia superficial, la copia profunda procesa todos los niveles del objeto de manera recursiva, creando una instancia completamente independiente con su propio espacio en memoria. Se puede implementar utilizando recursion.
Implementación básica con recursion:
function clonarObjeto(objeto) {
let copia = {};
for (let propiedad in objeto) {
// Si la propiedad es un objeto, aplicar recursion
if (typeof objeto[propiedad] === "object" && objeto[propiedad] !== null) {
copia[propiedad] = clonarObjeto(objeto[propiedad]);
} else {
copia[propiedad] = objeto[propiedad];
}
}
return copia;
}
Verificación del funcionamiento:
// 1. Crear un objeto con propiedades anidadas
var usuario = {
nombre: "Carlos López",
pasatiempo: "lectura",
edad: {
valor: 25,
unidad: "años"
}
};
// 2. Función de copia profunda
function clonarObjeto(objeto) {
let copia = {};
for (let propiedad in objeto) {
if (typeof objeto[propiedad] === "object" && objeto[propiedad] !== null) {
copia[propiedad] = clonarObjeto(objeto[propiedad]);
} else {
copia[propiedad] = objeto[propiedad];
}
}
return copia;
}
// 3. Ejecutar la función
var clon = clonarObjeto(usuario);
// 4. Modificar propiedad anidada
usuario.edad.valor = 99;
// 5. Mostrar resultado
console.log(clon);
El resultado confirma que la copia es independiente: los cambios en el objeto original no afectan al objeto clonado.
Manejo de arrays en copias profundas:
Los arrays también son tipos de referencia en JavaScript. Es necesario diferenciarlos de los objetos para crear la estructura correcta:
function clonarObjeto(objeto) {
// Determinar si es un array usando instanceof
if (objeto instanceof Array) {
var copia = [];
} else {
var copia = {};
}
for (let propiedad in objeto) {
if (typeof objeto[propiedad] === "object" && objeto[propiedad] !== null) {
copia[propiedad] = clonarObjeto(objeto[propiedad]);
} else {
copia[propiedad] = objeto[propiedad];
}
}
return copia;
}
Verificación con arrays:
var usuario = {
nombre: "Ana Martínez",
pasatiempo: "pintura",
detalles: {
habilidades: ["JavaScript", "Python", "Java"],
experiencia: 5
}
};
// Función de copia profunda
function clonarObjeto(objeto) {
if (objeto instanceof Array) {
var copia = [];
} else {
var copia = {};
}
for (let propiedad in objeto) {
if (typeof objeto[propiedad] === "object" && objeto[propiedad] !== null) {
copia[propiedad] = clonarObjeto(objeto[propiedad]);
} else {
copia[propiedad] = objeto[propiedad];
}
}
return copia;
}
var clon = clonarObjeto(usuario);
usuario.detalles.habilidades[1] = "TypeScript";
console.log(clon);
El resultado es correcto: el array dentro del objeto clonado mantiene sus valores originales.
También se puede utilizar el método Array.isArray() para determinar si un objeto es un array:
function clonarObjeto(objeto) {
if (Array.isArray(objeto)) {
var copia = [];
} else {
var copia = {};
}
for (let propiedad in objeto) {
if (typeof objeto[propiedad] === "object" && objeto[propiedad] !== null) {
copia[propiedad] = clonarObjeto(objeto[propiedad]);
} else {
copia[propiedad] = objeto[propiedad];
}
}
return copia;
}