Supprimer la valeur de l'objet sans mutation

96

Quel est le moyen efficace et court de supprimer une valeur d'un objet à une clé spécifique sans muter l'objet d'origine?

J'aimerais faire quelque chose comme:

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

Je sais qu'il existe de nombreuses bibliothèques d'immuabilité pour de telles tâches, mais j'aimerais m'en sortir sans bibliothèque. Mais pour ce faire, une exigence serait d'avoir un moyen simple et court qui puisse être utilisé dans tout le code, sans soustraire la méthode en tant que fonction d'utilité.

Par exemple, pour ajouter une valeur, je fais ce qui suit:

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

C'est assez court, facile à retenir et ne nécessite pas de fonction utilitaire.

Y a-t-il quelque chose comme ça pour supprimer une valeur? ES6 est le bienvenu.

Merci beaucoup!

Amann
la source
"Supprimer une valeur sans muter l'objet" n'a aucun sens. Vous ne pouvez pas retirer de quelque chose et le garder intact en même temps. En fait, vous faites une copie partielle.
JJJ

Réponses:

215

Mettre à jour:

Vous pouvez supprimer une propriété d'un objet avec une affectation de Destructuration délicate :

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

Cependant, si le nom de la propriété que vous souhaitez supprimer est statique, vous pouvez le supprimer avec une simple ligne:

let {lastname, ...o2} = o

Le moyen le plus simple est simplement de ou vous pouvez cloner votre objet avant de le muter:

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

Vous pouvez également utiliser la omitfonction de la lodashbibliothèque utilitaire :

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

Il est disponible dans le cadre du package lodash ou en tant que package lodash.omit autonome .

Léonid Beschastny
la source
3
Est-ce vraiment la solution la plus simple? Par exemple, pour les tableaux, vous pouvez a2 = a1.filter(el => el.id !== id)omettre un élément dans un tableau par un identifiant spécifique. N'y a-t-il pas une telle chose pour un objet?
amann
1
Je suis d'accord avec @amann. Soit il existe une meilleure solution, soit ES6 a vraiment manqué quelque chose à propos de rendre JS utilisable de manière plus fonctionnelle. Par exemple. Ruby haskeep_if
Augustin Riedinger
1
@AugustinRiedinger en fait, il y a un moyen. Voir ma mise à jour.
Leonid Beschastny
J'allais suggérer la même chose en fonction de cette question: stackoverflow.com/questions/35152522/…
Augustin Riedinger
1
La solution de déstructuration mise à jour est excellente. Bien que mon esprit en soit un peu époustouflé. Pourquoi ça ne const {[prop], ...rest} = objmarche pas ? Pourquoi devez-vous affecter la propriété extraite à une nouvelle variable ( omitdans ce cas)?
branweb
28

Avec la déstructuration d'objets ES7:

const myObject = {
  a: 1,
  b: 2,
  c: 3
};
const { a, ...noA } = myObject;
console.log(noA); // => { b: 2, c: 3 }
senbon
la source
1
et si aest stocké dans une variable const x = 'a'?
FFF
Ne change rien. a fait uniquement référence au premier élément du tableau. Ça pourrait être comme ça: const { a,b, ...noA } = myObject; console.log(noA); // => {c: 3 }
senbon
1
c'est la solution la plus simple et la plus élégante
Joe
1
Y a-t-il un moyen de faire cela si les touches sont des nombres?
Marc Sloth Eastman
22

solution une ligne

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

Comme suggéré dans les commentaires ci-dessus, si vous souhaitez étendre cela pour supprimer plus d'un élément de votre objectj'aime utiliser filter. etreduce

par exemple

    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
la source
1
Pourquoi n'appliquez-vous pas la condition de filtre dans la réduction, afin de pouvoir vous sauver une boucle?
Ivo Sabev
merci pour le conseil @IvoSabev J'ai quelques fonctions dans mon code où je filtre puis réduis, mais je prendrai en considération votre suggestion à l'avenir pour enregistrer la boucle.
ak85
3

Pour ajouter du piquant apportant de la performance. Vérifiez ce fil ci-dessous

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

L'utilisation de l'opérateur de suppression a des effets négatifs sur les performances du modèle de classes cachées V8. En général, il est recommandé de ne pas l'utiliser.

Alternativement, pour supprimer les propriétés énumérables propres à l'objet, nous pourrions créer une nouvelle copie d'objet sans ces propriétés (exemple en utilisant lodash):

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

Ou même définissez la valeur de la propriété sur null ou undefined (ce qui est implicitement ignoré lors de la sérialisation vers JSON):

o.prop = indéfini

Vous pouvez aussi utiliser la méthode destructrice

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

Et un exemple plus pratique:

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

Comme vous pouvez le voir, vous pouvez utiliser la [somePropsVarForDynamicName]: scopeVarNamesyntaxe pour les noms dynamiques. Et vous pouvez tout mettre entre crochets (nouveau bloc) pour que le reste soit récupéré après.

Voici un test: entrez la description de l'image ici

exec:

entrez la description de l'image ici

Ou nous pouvons aller avec une fonction comme

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;
    }, {});
}

pour dactylographié

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;
    }, {});
}

Usage:

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

a = deleteProps(a, 'propB'); 

// or 

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

De cette façon, un nouvel objet est créé. Et la propriété rapide de l'objet est conservée. Ce qui peut être important ou important. Si le mappage et l'objet seront consultés plusieurs fois.

L'association undefinedpeut également être une bonne façon de procéder. Quand vous pouvez vous le permettre. Et pour les clés, vous pouvez également vérifier la valeur. Par exemple, pour obtenir toutes les clés actives, vous faites quelque chose comme:

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 ne convient pas pour une grande liste. Ou le développement au fil du temps avec de nombreux accessoires à venir. Comme l'utilisation de la mémoire continuera d'augmenter et ne sera jamais nettoyée. Cela dépend donc de l'utilisation. Et simplement créer un nouvel objet semble être le bon moyen.

Ensuite, la Premature optimization is the root of all evilvolonté entrera en jeu. Vous devez donc être conscient du compromis. Et ce qui est nécessaire et ce qui ne l'est pas.

Remarque sur _.omit () de lodash

Il est supprimé de la version 5. Vous ne pouvez pas le trouver dans le dépôt. Et voici un numéro qui en parle.

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

v8

Vous pouvez vérifier ceci qui est une bonne lecture https://v8.dev/blog/fast-properties

Mohamed Allal
la source
0

avec lodash clone

(Remarque: le clone lodash peut être utilisé à la place pour les objets peu profonds)

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
la source
0

Pour mon code, je voulais une version courte pour la valeur de retour de map () mais les solutions d'opérations multilignes / mutli étaient "moche". La caractéristique clé est l'ancien void(0)qui se résout à undefined.

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

La propriété reste dans l'objet:

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

mais le framework de transmission le tue pour moi (bc stringify):

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

Mon problème avec la réponse acceptée, à partir d'une norme de règle ESLint, si vous essayez de déstructurer:

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

les 2 nouvelles variables, notNeededet alsoNotNeededpeut générer un avertissement ou une erreur en fonction de votre configuration car elles sont maintenant inutilisées. Alors pourquoi créer de nouveaux vars si inutilisés?

Je pense que vous devez deletevraiment utiliser la fonction.

Phil Lucks
la source
3
La no-unused-varsrègle a en fait une option ignoreRestSiblingsmaintenant pour couvrir ce cas d'utilisation: eslint.org/docs/rules/no-unused-vars#ignorerestsiblings
amann le