Eliminar valor del objeto sin mutación

102

¿Cuál es una forma buena y breve de eliminar un valor de un objeto en una clave específica sin alterar el objeto original?

Me gustaría hacer algo como:

let o = {firstname: 'Jane', lastname: 'Doe'};
let o2 = doSomething(o, 'lastname');
console.log(o.lastname); // 'Doe'
console.log(o2.lastname); // undefined

Sé que hay muchas bibliotecas de inmutabilidad para tales tareas, pero me gustaría salirme sin una biblioteca. Pero para hacer esto, un requisito sería tener una forma fácil y corta que pueda usarse en todo el código, sin abstraer el método como una función de utilidad.

Por ejemplo, para agregar un valor, hago lo siguiente:

let o2 = {...o1, age: 31};

Es bastante breve, fácil de recordar y no necesita una función de utilidad.

¿Hay algo como esto para eliminar un valor? ES6 es muy bienvenido.

¡Muchas gracias!

amann
fuente
"Eliminar un valor sin mutar el objeto" no tiene ningún sentido. No se puede quitar de algo y mantenerlo intacto al mismo tiempo. Lo que estás haciendo en realidad es hacer una copia parcial.
JJJ

Respuestas:

226

Actualizar:

Puede eliminar una propiedad de un objeto con una tarea de Desestructuración complicada :

const doSomething = (obj, prop) => {
  let {[prop]: omit, ...res} = obj
  return res
}

Sin embargo, si el nombre de propiedad que desea eliminar es estático, puede eliminarlo con una simple línea:

let {lastname, ...o2} = o

La forma más fácil es simplemente O puede clonar su objeto antes de mutarlo:

const doSomething = (obj, prop) => {
  let res = Object.assign({}, obj)
  delete res[prop]
  return res
}

Alternativamente, puede usar la omitfunción de lodashla biblioteca de utilidades :

let o2 = _.omit(o, 'lastname')

Está disponible como parte del paquete lodash o como paquete lodash.omit independiente .

Leonid Beschastny
fuente
3
¿Es esa realmente la solución más simple? Por ejemplo, para matrices, puede a2 = a1.filter(el => el.id !== id)omitir un elemento dentro de una matriz mediante una identificación específica. ¿No existe tal cosa para un objeto?
amann
1
Estoy de acuerdo con @amann. O bien no es una solución mejor, o ES6 mucho de menos algo sobre la fabricación de JS utilizar de manera más funcional. P.ej. Ruby tienekeep_if
Augustin Riedinger
1
@AugustinRiedinger en realidad, hay una manera. Ver mi actualización.
Leonid Beschastny
2
La solución de desestructuración actualizada es excelente. Aunque mi mente está un poco alucinada por eso. ¿Por qué no const {[prop], ...rest} = objfunciona? ¿Por qué tiene que asignar la propiedad extraída a una nueva variable ( omiten este caso)?
branweb
1
@ Antoni4 puede agregar "omitir" u "omitir" a su no-unused-varsregla, es solo una expresión regular.
adrianmc
35

Con la desestructuración de objetos ES7:

const myObject = {
  a: 1,
  b: 2,
  c: 3
};
const { a, ...noA } = myObject;
console.log(noA); // => { b: 2, c: 3 }
senbon
fuente
1
¿y si ase almacena en una variable const x = 'a'?
FFF
No cambia nada. a solo se refiere al primer elemento de la matriz. Podría ser así: const { a,b, ...noA } = myObject; console.log(noA); // => {c: 3 }
senbon
1
esta es la solución más elegante y simple
Joe
1
¿Hay alguna forma de hacer esto si las claves son números?
Marc Sloth Eastman
25

solución de una línea

const removeKey = (key, {[key]: _, ...rest}) => rest;
punksta
fuente
4

Como se sugiere en los comentarios anteriores, si desea extender esto para eliminar más de un elemento de su objectMe gusta usar filter. yreduce

p.ej

    const o = {
      "firstname": "Jane",
      "lastname": "Doe",
      "middlename": "Kate",
      "age": 23,
      "_id": "599ad9f8ebe5183011f70835",
      "index": 0,
      "guid": "1dbb6a4e-f82d-4e32-bb4c-15ed783c70ca",
      "isActive": true,
      "balance": "$1,510.89",
      "picture": "http://placehold.it/32x32",
      "eyeColor": "green",
      "registered": "2014-08-17T09:21:18 -10:00",
      "tags": [
        "consequat",
        "ut",
        "qui",
        "nulla",
        "do",
        "sunt",
        "anim"
      ]
    };

    const removeItems = ['balance', 'picture', 'tags']
    console.log(formatObj(o, removeItems))

    function formatObj(obj, removeItems) {
      return {
        ...Object.keys(obj)
          .filter(item => !isInArray(item, removeItems))
          .reduce((newObj, item) => {
            return {
              ...newObj, [item]: obj[item]
            }
          }, {})
      }
    }

    function isInArray(value, array) {
      return array.indexOf(value) > -1;
    }

ak85
fuente
1
¿Por qué no aplica la condición de filtro en la reducción, para que pueda ahorrarse un bucle?
Ivo Sabev
gracias por la sugerencia @IvoSabev Tengo algunas funciones en mi código donde filtro y luego reduzco, pero consideraré su sugerencia en el futuro para guardar el ciclo.
ak85
4

Para agregar un poco de sabor a rendimiento. Compruebe este hilo a continuación

https://github.com/googleapis/google-api-nodejs-client/issues/375

El uso del operador de eliminación tiene efectos negativos en el rendimiento del patrón de clases ocultas de V8. En general se recomienda no utilizarlo.

Alternativamente, para eliminar las propiedades enumerables propias del objeto, podríamos crear una nueva copia del objeto sin esas propiedades (ejemplo usando lodash):

_.omit (o, 'prop', 'prop2')

O incluso defina el valor de la propiedad en nulo o indefinido (que se ignora implícitamente al serializar a JSON):

o.prop = indefinido

También puedes usar la forma destructiva

const {remov1, remov2, ...new} = old;
old = new;

Y un ejemplo más práctico:

this._volumes[this._minCandle] = undefined;
{ 
     const {[this._minCandle]: remove, ...rest} = this._volumes;
     this._volumes = rest; 
}

Como puede ver, puede usar [somePropsVarForDynamicName]: scopeVarNamesintaxis para nombres dinámicos. Y puede poner todo entre corchetes (nuevo bloque) para que el resto sea basura recolectada después.

Aquí una prueba: ingrese la descripción de la imagen aquí

ejecutivo:

ingrese la descripción de la imagen aquí

O podemos ir con alguna función como

function deleteProps(obj, props) {
    if (!Array.isArray(props)) props = [props];
    return Object.keys(obj).reduce((newObj, prop) => {
        if (!props.includes(prop)) {
            newObj[prop] = obj[prop];
        }
        return newObj;
    }, {});
}

para mecanografiar

function deleteProps(obj: Object, props: string[]) {
    if (!Array.isArray(props)) props = [props];
    return Object.keys(obj).reduce((newObj, prop) => {
        if (!props.includes(prop)) {
            newObj[prop] = obj[prop];
        }
        return newObj;
    }, {});
}

Uso:

let a = {propH: 'hi', propB: 'bye', propO: 'ok'};

a = deleteProps(a, 'propB'); 

// or 

a = deleteProps(a, ['propB', 'propO']);

De esta forma se crea un nuevo objeto. Y se mantiene la propiedad rápida del objeto. Lo que puede ser importante o importante. Si se accederá al mapeo y al objeto muchas veces.

También asociarse undefinedpuede ser una buena manera de hacerlo. Cuando te lo puedas permitir. Y para las claves, también puede verificar el valor. Por ejemplo, para obtener todas las claves activas, haga algo como:

const allActiveKeys = Object.keys(myObj).filter(k => myObj[k] !== undefined);
//or
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k]); // if any false evaluated value is to be stripped.

Undefined no es adecuado para una lista grande. O desarrollo a lo largo del tiempo con muchos accesorios por venir. Como el uso de la memoria seguirá creciendo y nunca se limpiará. Entonces depende del uso. Y simplemente crear un objeto nuevo parece ser la mejor manera.

Entonces se Premature optimization is the root of all evilactivará. Por lo tanto, debe ser consciente de la compensación. Y lo que se necesita y lo que no.

Nota sobre _.omit () de lodash

Se eliminó de la versión 5. No puede encontrarlo en el repositorio. Y aquí un tema del que hablar.

https://github.com/lodash/lodash/issues/2930

v8

Puede comprobar esto, que es una buena lectura https://v8.dev/blog/fast-properties

Mohamed Allal
fuente
1

Mi problema con la respuesta aceptada, de un estándar de reglas ESLint, si intenta desestructurar:

    const { notNeeded, alsoNotNeeded, ...rest } = { ...ogObject };

las 2 nuevas variables, notNeededy alsoNotNeededpueden arrojar una advertencia o error dependiendo de su configuración, ya que ahora no se utilizan. Entonces, ¿por qué crear nuevas vars si no se utilizan?

Creo que necesitas usar la deletefunción de verdad.

Phil suertes
fuente
4
La no-unused-varsregla tiene ignoreRestSiblingsahora una opción para cubrir este caso de uso: eslint.org/docs/rules/no-unused-vars#ignorerestsiblings
amann
0

con lodash cloneDeep y eliminar

(nota: el clon lodash se puede usar en su lugar para objetos poco profundos)

const obj = {a: 1, b: 2, c: 3}
const unwantedKey = 'a'

const _ = require('lodash')
const objCopy = _.cloneDeep(obj)
delete objCopy[unwantedKey]
// objCopy = {b: 2, c: 3}
FFF
fuente
0

Para mi código, quería una versión corta para el valor de retorno de map () pero las soluciones de operaciones multilínea / mutli eran "feas". La característica clave es lo antiguo void(0)que se resuelve undefined.

let o2 = {...o, age: 31, lastname: void(0)};

La propiedad permanece en el objeto:

console.log(o2) // {firstname: "Jane", lastname: undefined, age: 31}

pero el marco de transmisión lo mata por mí (bc stringify):

console.log(JSON.stringify(o2)) // {"firstname":"Jane","age":31}
Jonny
fuente