Índice
- Concursante A: "Me puse ca undefined".
- Concursante B: "Utilicé el deleteoperador".
- Concursante C: "Eliminé la propiedad a través de un Proxyobjeto".
- Concursante D: "Evité la mutación mediante el uso de la desestructuración de objetos".
- Concursante E: "Usé JSON.stringifyy JSON.parse".
- Concursante F: "Confiamos en Lodash en mi empresa".
- Conclusión
Quitar propiedades de un objeto en JavaScript puede no ser el trabajo más apasionante, pero hay muchas maneras de lograrlo, cada una de las cuales revela un aspecto fundamental de cómo funciona JavaScript. Juan Diego Rodríguez explora cada técnica en este artículo.
Se pide a un grupo de concursantes que completen la siguiente tarea:
Hacer object1
similar a object2
.
let object1 = { a: "hello", b: "world", c: "!!!",};let object2 = { a: "hello", b: "world",};
Parece fácil, ¿verdad? Simplemente elimine la c
propiedad que coincida object2
. Sorprendentemente, cada persona describió una solución diferente:
- Concursante A: "Me puse
c
aundefined
". - Concursante B: "Usé el
delete
operador". - Concursante C: "Eliminé la propiedad a través de un
Proxy
objeto". - Concursante D: "Evité la mutación mediante el uso de la desestructuración de objetos".
- Concursante E: "Usé
JSON.stringify
yJSON.parse
". - Concursante F: "Confiamos en Lodash en mi empresa".
Se dieron muchísimas respuestas y todas parecen ser opciones válidas. Entonces, ¿quién tiene “razón”? Analicemos cada enfoque.
Concursante A: "Me puse ca undefined".
En JavaScript, acceder a una propiedad no existente devuelve undefined
.
const movie = { name: "Up",};console.log(movie.premiere); // undefined
Es fácil pensar que establecer una propiedad en la undefined
elimina del objeto. Pero si intentamos hacer eso, observaremos un pequeño pero importante detalle:
const movie = { name: "Up", premiere: 2009,};movie.premiere = undefined;console.log(movie);
Aquí está el resultado que obtenemos:
{name: 'up', premiere: undefined}
Como puede ver, premiere
todavía existe dentro del objeto incluso cuando es undefined
. En realidad, este enfoque no elimina la propiedad sino que cambia su valor. Podemos confirmar que usando el hasOwnProperty()
método:
const propertyExists = movie.hasOwnProperty("premiere");console.log(propertyExists); // true
Pero entonces, ¿por qué, en nuestro primer ejemplo, el acceso object.premiere
regresa undefined
si la propiedad no existe en el objeto? ¿No debería arrojar un error como al acceder a una variable inexistente?
console.log(iDontExist);// Uncaught ReferenceError: iDontExist is not defined
La respuesta está en cómo ReferenceError
se comporta y qué referencia es, en primer lugar.
Una referencia es un enlace de nombre resuelto que indica dónde se almacena un valor. Consta de tres componentes: un valor base , el nombre al que se hace referencia y un indicador de referencia estricto .
Para una user.name
referencia, el valor base es el objeto, user
mientras que el nombre al que se hace referencia es la cadena, name
y el indicador de referencia estricta es false
si el código no está en strict mode
.
Las variables se comportan de manera diferente. No tienen un objeto principal, por lo que su valor base es un registro de entorno , es decir, un valor base único asignado cada vez que se ejecuta el código.
Si intentamos acceder a algo que no tiene un valor base, JavaScript arrojará un archivo ReferenceError
. Sin embargo, si se encuentra un valor base, pero el nombre al que se hace referencia no apunta a un valor existente, JavaScript simplemente asignará el valor undefined
.
“El tipo Indefinido tiene exactamente un valor, llamado indefinido . Cualquier variable a la que no se le haya asignado un valor tiene el valor indefinido ”.
— Especificación ECMAScript
¡ Podríamos dedicar un artículo entero a abordar undefined
las travesuras!
Concursante B: "Utilicé el deleteoperador".
El delete
único propósito del operador es eliminar una propiedad de un objeto y regresar true
si el elemento se elimina con éxito.
const dog = { breed: "bulldog", fur: "white",};delete dog.fur;console.log(dog); // {breed: 'bulldog'}
Algunas advertencias vienen con el delete
operador que debemos tener en cuenta antes de usarlo. Primero, el delete
operador se puede utilizar para eliminar un elemento de una matriz. Sin embargo, deja una ranura vacía dentro de la matriz, lo que puede causar un comportamiento inesperado ya que propiedades como length
no se actualizan y aún cuentan la ranura abierta.
const movies = ["Interstellar", "Top Gun", "The Martian", "Speed"];delete movies[2];console.log(movies); // ['Interstellar', 'Top Gun', empty, 'Speed']console.log(movies.length); // 4
En segundo lugar, imaginemos el siguiente objeto anidado:
const user = { name: "John", birthday: {day: 14, month: 2},};
Intentar eliminar la birthday
propiedad usando el delete
operador funcionará bien, pero existe la idea errónea de que al hacerlo se libera la memoria asignada para el objeto.
En el ejemplo anterior, birthday
es una propiedad que contiene un objeto anidado. Los objetos en JavaScript se comportan de manera diferente a los valores primitivos (por ejemplo, números, cadenas y valores booleanos) en cuanto a cómo se almacenan en la memoria. Se almacenan y copian "por referencia", mientras que los valores primitivos se copian de forma independiente como un valor completo.
Tomemos, por ejemplo, un valor primitivo como una cadena:
let movie = "Home Alone";let bestSeller = movie;
En este caso, cada variable tiene un espacio independiente en la memoria. Podemos ver este comportamiento si intentamos reasignar uno de ellos:
movie = "Terminator";console.log(movie); // "Terminator"console.log(bestSeller); // "Home Alone"
En este caso la reasignación movie
no afecta bestSeller
ya que están en dos espacios diferentes en la memoria. Las propiedades o variables que contienen objetos (por ejemplo, objetos normales, matrices y funciones) son referencias que apuntan a un único espacio en la memoria. Si intentamos copiar un objeto, simplemente estamos duplicando su referencia.
let movie = {title: "Home Alone"};let bestSeller = movie;bestSeller.title = "Terminator";console.log(movie); // {title: "Terminator"}console.log(bestSeller); // {title: "Terminator"}
Como puede ver, ahora son objetos y reasignar una bestSeller
propiedad también cambia el movie
resultado. Debajo del capó, JavaScript mira el objeto real en la memoria y realiza el cambio, y ambas referencias apuntan al objeto modificado.
Al saber cómo se comportan los objetos "por referencia", ahora podemos entender cómo el uso del delete
operador no libera espacio en la memoria.
El proceso por el cual los lenguajes de programación liberan memoria se llama recolección de basura . En JavaScript, la memoria se libera para un objeto cuando no hay más referencias y se vuelve inalcanzable . Por lo tanto, el uso del delete
operador puede hacer que el espacio de la propiedad sea elegible para la recopilación, pero puede haber más referencias que impidan que se elimine de la memoria.
Ya que estamos en el tema, vale la pena señalar que existe un cierto debate en torno al delete
impacto del operador en el rendimiento . Puedes seguir el rastro del conejo desde el enlace, pero seguiré adelante y te estropearé el final: la diferencia en el rendimiento es tan insignificante que no plantearía un problema en la gran mayoría de los casos de uso. Personalmente, considero que el enfoque idiomático y directo del operador es una victoria sobre un minúsculo impacto en el rendimiento.
Dicho esto, se puede argumentar en contra del uso delete
ya que muta un objeto. En general, es una buena práctica evitar las mutaciones, ya que pueden provocar un comportamiento inesperado en el que una variable no tiene el valor que asumimos que tiene.
Concursante C: "Eliminé la propiedad a través de un Proxyobjeto".
Este concursante definitivamente fue un fanfarrón y usó un sustituto para su respuesta. Un proxy es una forma de insertar una lógica intermedia entre las operaciones comunes de un objeto, como obtener, configurar, definir y, sí, eliminar propiedades. Funciona a través del Proxy
constructor que toma dos parámetros:
target
: El objeto desde donde queremos crear un proxy.handler
: Un objeto que contiene la lógica intermedia para las operaciones.
Dentro de handler
, definimos métodos para las diferentes operaciones, llamados trampas , porque interceptan la operación original y realizan un cambio personalizado. El constructor devolverá un Proxy
objeto, un objeto idéntico a target
, pero con la lógica intermedia agregada.
const cat = { breed: "siamese", age: 3,};const handler = { get(target, property) { return `cat's ${property} is ${target[property]}`; },};const catProxy = new Proxy(cat, handler);console.log(catProxy.breed); // cat's breed is siameseconsole.log(catProxy.age); // cat's age is 3
Aquí, handler
modifica la operación de obtención para devolver un valor personalizado.Te recomendamos Aprender a programar con ejemplos
Digamos que queremos registrar la propiedad que estamos eliminando en la consola cada vez que usamos el delete
operador. Podemos agregar esta lógica personalizada a través de un proxy usando la deleteProperty
trampa.
const product = { name: "vase", price: 10,};const handler = { deleteProperty(target, property) { console.log(`Deleting property: ${property}`); },};const productProxy = new Proxy(product, handler);delete productProxy.name; // Deleting property: name
El nombre de la propiedad se registra en la consola pero arroja un error en el proceso:
Uncaught TypeError: 'deleteProperty' on proxy: trap returned falsish for property 'name'
El error se produce porque el controlador no tenía un return
valor. Eso significa que por defecto es undefined
. En modo estricto, si el delete
operador devuelve false
, generará un error y undefined
, al ser un valor falso, desencadena este comportamiento.
Si intentamos regresar true
para evitar el error, encontraremos un tipo diferente de problema:
// ...const handler = { deleteProperty(target, property) { console.log(`Deleting property: ${property}`); return true; },};const productProxy = new Proxy(product, handler);delete productProxy.name; // Deleting property: nameconsole.log(productProxy); // {name: 'vase', price: 10}
¡La propiedad no se elimina!
Reemplazamos el delete
comportamiento predeterminado del operador con este código, por lo que no recuerda que tiene que "eliminar" la propiedad.
Aquí es donde Reflect
entra en juego.
Reflect
es un objeto global con una colección de todos los métodos internos de un objeto. Sus métodos se pueden usar como operaciones normales en cualquier lugar, pero están destinados a usarse dentro de un proxy.
Por ejemplo, podemos resolver el problema en nuestro código devolviendo Reflect.deleteProperty()
(es decir, la Reflect
versión del delete
operador) dentro del controlador.
const product = { name: "vase", price: 10,};const handler = { deleteProperty(target, property) { console.log(`Deleting property: ${property}`); return Reflect.deleteProperty(target, property); },};const productProxy = new Proxy(product, handler);delete productProxy.name; // Deleting property: nameconsole.log(product); // {price: 10}
Vale la pena señalar que ciertos objetos, como Math
, Date
y JSON
, tienen propiedades que no se pueden eliminar mediante el delete
operador ni ningún otro método. Estas son propiedades de objetos "no configurables", lo que significa que no se pueden reasignar ni eliminar. Si intentamos utilizar el delete
operador en una propiedad no configurable, fallará silenciosamente y devolverá false
o arrojará un error si ejecutamos nuestro código en modo estricto.
"use strict";delete Math.PI;
Producción:
Uncaught TypeError: Cannot delete property 'PI' of #Object
Si queremos evitar errores con el delete
operador y las propiedades no configurables, podemos usar el Reflect.deleteProperty()
método ya que no arroja un error al intentar eliminar una propiedad no configurable, incluso en modo estricto, porque falla silenciosamente.
Supongo, sin embargo, que preferiría saber cuándo está intentando eliminar un objeto global en lugar de evitar el error.
Concursante D: "Evité la mutación mediante el uso de la desestructuración de objetos".
La desestructuración de objetos es una sintaxis de asignación que extrae las propiedades de un objeto en variables individuales. Utiliza una notación de llaves (
{}
) en el lado izquierdo de una tarea para indicar cuál de las propiedades obtener.
const movie = { title: "Avatar", genre: "science fiction",};const {title, genre} = movie;console.log(title); // Avatarconsole.log(genre); // science fiction
También funciona con matrices usando corchetes ( []
):
const animals = ["dog", "cat", "snake", "elephant"];const [a, b] = animals;console.log(a); // dogconsole.log(b); // cat
La sintaxis extendida ( ...
) es algo así como la operación opuesta porque encapsula varias propiedades en un objeto o una matriz si son valores únicos.
Podemos usar la desestructuración de objetos para descomprimir los valores de nuestro objeto y la sintaxis extendida para conservar solo los que queremos:
const car = { type: "truck", color: "black", doors: 4};const {color, ...newCar} = car;console.log(newCar); // {type: 'truck', doors: 4}
¡De esta manera, evitamos tener que mutar nuestros objetos y los posibles efectos secundarios que conlleva!
Aquí hay un caso extremo con este enfoque: eliminar una propiedad solo cuando es undefined
. Gracias a la flexibilidad de la desestructuración de objetos, podemos eliminar propiedades cuando lo son undefined
(o false , para ser exactos).
Imagine que tiene una tienda en línea con una amplia base de datos de productos. Tienes una función para encontrarlos. Por supuesto, necesitará algunos parámetros, tal vez el nombre del producto y la categoría.
const find = (product, category) = { const options = { limit: 10, product, category, }; console.log(options); // Find in database...};
En este ejemplo, el producto name
debe ser proporcionado por el usuario para realizar la consulta, pero category
es opcional. Entonces, podríamos llamar a la función así:
find("bedsheets");
Y como category
no se especifica a, devuelve como undefined
, lo que da como resultado el siguiente resultado:
{limit: 10, product: 'beds', category: undefined}
En este caso, no deberíamos usar parámetros predeterminados porque no buscamos una categoría específica.
Observe cómo la base de datos podría asumir incorrectamente que estamos consultando productos en una categoría llamada undefined
! Esto conduciría a un resultado vacío, que es un efecto secundario no deseado. Aunque muchas bases de datos filtrarán la undefined
propiedad por nosotros, sería mejor desinfectar las opciones antes de realizar la consulta. Una forma interesante de eliminar dinámicamente una undefined
propiedad es mediante la destrucción de objetos junto con el AND
operador ( ).
En lugar de escribir options
así:
const options = { limit: 10, product, category,};
…podemos hacer esto en su lugar:
const options = { limit: 10, product, ...(category {category}),};
Puede parecer una expresión compleja, pero después de comprender cada parte, se convierte en una frase sencilla. Lo que estamos haciendo es aprovecharnos del operador.
El AND
operador se usa principalmente en declaraciones condicionales para decir:
Si
A
yB
sontrue
, entonces haz esto.
Pero en esencia, evalúa dos expresiones de izquierda a derecha, devolviendo la expresión de la izquierda si es falsa y la expresión de la derecha si ambas son verdaderas . Entonces, en nuestro ejemplo anterior, el AND
operador tiene dos casos:
category
esundefined
(o falso );category
se define.
En el primer caso en el que es falso , el operador devuelve la expresión de la izquierda category
. Si conectamos category
el resto del objeto, se evalúa de esta manera:
const options = { limit: 10, product, ...category,};
Y si intentamos desestructurar cualquier valor falso dentro de un objeto, se desestructurará hasta convertirlo en nada:
const options = { limit: 10, product,};
En el segundo caso, dado que el operador es veraz , devuelve la expresión de la derecha {category}
. Cuando se conecta al objeto, se evalúa de esta manera:
const options = { limit: 10, product, ...{category},};
Y como category
está definido, se desestructura en una propiedad normal:
const options = { limit: 10, product, category,};
Poniéndolo todo junto, obtenemos la siguiente betterFind()
función:
const betterFind = (product, category) = { const options = { limit: 10, product, ...(category {category}), }; console.log(options); // Find in a database...};betterFind("sofas");
Y si no especificamos ninguno category
, simplemente no aparece en el options
objeto final.
{limit: 10, product: 'sofas'}
Concursante E: "Usé JSON.stringifyy JSON.parse".
Sorprendentemente para mí, hay una manera de eliminar una propiedad reasignándola a undefined
. El siguiente código hace exactamente eso:
let monitor = { size: 24, screen: "OLED",};monitor.screen = undefined;monitor = JSON.parse(JSON.stringify(monitor));console.log(monitor); // {size: 24}
En cierto modo te mentí ya que estamos empleando algunas travesuras de JSON para lograr este truco, pero podemos aprender algo útil e interesante de ellas.
Aunque JSON se inspira directamente en JavaScript, se diferencia en que tiene una sintaxis fuertemente tipada. No permite funciones ni undefined
valores, por lo que su uso JSON.stringify()
omitirá todos los valores no válidos durante la conversión, lo que dará como resultado texto JSON sin las undefined
propiedades. A partir de ahí, podemos analizar el texto JSON en un objeto JavaScript usando el JSON.parse()
método.
Es importante conocer las limitaciones de este enfoque. Por ejemplo, JSON.stringify()
omite funciones y genera un error si se encuentra una referencia circular (es decir, una propiedad hace referencia a su objeto principal) o un BigInt
valor.
Concursante F: "Confiamos en Lodash en mi empresa".
Vale la pena señalar que las bibliotecas de utilidades como Lodash.js, Underscore.js o Ramda también proporcionan métodos para eliminar (o pick()
) propiedades de un objeto. No analizaremos diferentes ejemplos para cada biblioteca ya que su documentación ya hace un excelente trabajo al respecto.
Conclusión
Volviendo a nuestro escenario inicial, ¿qué concursante tiene razón?
La respuesta: ¡ Todos! Bueno, excepto el primer concursante. Establecer una propiedad en undefined
just no es un enfoque que queramos considerar para eliminar una propiedad de un objeto, dadas todas las otras formas que tenemos de hacerlo.
Como ocurre con la mayoría de las cosas en desarrollo, el enfoque más "correcto" depende de la situación. Pero lo interesante es que detrás de cada enfoque hay una lección sobre la naturaleza misma de JavaScript. Comprender todas las formas de eliminar una propiedad en JavaScript puede enseñarnos aspectos fundamentales de la programación y JavaScript, como la administración de memoria, la recolección de basura, los servidores proxy, JSON y la mutación de objetos. ¡Eso es mucho aprendizaje para algo aparentemente tan aburrido y trivial!
Lecturas adicionales sobre SmashingMag
- “ Descubrimiento de objetos primitivos en JavaScript (Parte 1) ”, Kirill Myshkin
- “ Objetos primitivos en JavaScript: cuándo usarlos (Parte 2) ”, Kirill Myshkin
- “ Una reintroducción a la tarea desestructurante ”, Laurie Barth
- “ Geometría del modelo de objetos de documento (DOM): introducción y guía para principiantes ”, Pearl Akpan
(gg, yk)Explora más en
- javascript
- Herramientas
- Técnicas
Tal vez te puede interesar:
- ¿Deberían abrirse los enlaces en ventanas nuevas?
- 24 excelentes tutoriales de AJAX
- 70 técnicas nuevas y útiles de AJAX y JavaScript
- Más de 45 excelentes recursos y repositorios de fragmentos de código
Lo que nos dice la eliminación de propiedades de objetos sobre JavaScript
Concursante A: 'Me puse ca undefined'.Concursante B: 'Utilicé el deleteoperador'.Concursante C: 'Eliminé la propiedad a través de un Proxyobjeto'.Concursant
programar
es
2025-01-08
Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente