Usuń wartość z obiektu bez mutacji

102

Jaki jest dobry i krótki sposób na usunięcie wartości z obiektu o określonym kluczu bez mutowania oryginalnego obiektu?

Chciałbym zrobić coś takiego:

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

Wiem, że istnieje wiele bibliotek niezmienności do takich zadań, ale chciałbym uciec bez biblioteki. Jednak aby to zrobić, wymagałoby to łatwego i krótkiego sposobu, który może być używany w całym kodzie, bez abstrahowania metody jako funkcji użyteczności.

Np. Aby dodać wartość, wykonuję następujące czynności:

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

Jest to dość krótkie, łatwe do zapamiętania i nie wymaga funkcji narzędziowej.

Czy jest coś takiego do usuwania wartości? ES6 jest bardzo mile widziane.

Dziękuję Ci bardzo!

amann
źródło
„Usuń wartość bez modyfikowania obiektu” nie ma sensu. Nie można go jednocześnie usunąć i zachować w stanie nienaruszonym. W rzeczywistości robisz częściową kopię.
JJJ

Odpowiedzi:

226

Aktualizacja:

Możesz usunąć właściwość z obiektu za pomocą trudnego przypisania Destrukturyzacja :

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

Jeśli jednak nazwa właściwości, którą chcesz usunąć, jest statyczna, możesz ją usunąć za pomocą prostego, jednowierszowego:

let {lastname, ...o2} = o

Najłatwiej jest po prostu lub możesz sklonować obiekt przed jego mutacją:

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

Alternatywnie możesz użyć omitfunkcji z lodashbiblioteki narzędziowej :

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

Jest dostępny jako część pakietu lodash lub jako samodzielny pakiet lodash.omit .

Leonid Beschastny
źródło
3
Czy to naprawdę najprostsze rozwiązanie? Np. W przypadku tablic możesz a2 = a1.filter(el => el.id !== id)pominąć element w tablicy za pomocą określonego identyfikatora. Czy nie ma czegoś takiego dla przedmiotu?
amann
1
Zgadzam się z @amann. Albo jest lepsze rozwiązanie, albo w ES6 naprawdę brakowało czegoś w sprawianiu, by JS był użyteczny w bardziej funkcjonalny sposób. Na przykład. Ruby makeep_if
Augustin Riedinger
1
@AugustinRiedinger właściwie, jest sposób. Zobacz moją aktualizację.
Leonid Beschastny
2
Zaktualizowane rozwiązanie destrukturyzujące jest świetne. Chociaż mój umysł jest przez to trochę poruszony. Dlaczego nie const {[prop], ...rest} = objdziała? Dlaczego musisz przypisać wyodrębnioną właściwość do nowej zmiennej ( omitw tym przypadku)?
branweb
1
@ Antoni4 możesz dodać do swojej no-unused-varsreguły „pomiń” lub „pominięty” , to po prostu wyrażenie regularne.
adrianmc
35

Dzięki destrukturyzacji obiektów ES7:

const myObject = {
  a: 1,
  b: 2,
  c: 3
};
const { a, ...noA } = myObject;
console.log(noA); // => { b: 2, c: 3 }
senbon
źródło
1
co jeśli ajest przechowywany w zmiennej const x = 'a'?
FFF
Nic nie zmienia. a odnosi się tylko do pierwszego elementu tablicy. Mogłoby to wyglądać tak: const { a,b, ...noA } = myObject; console.log(noA); // => {c: 3 }
senbon
1
to jest najbardziej eleganckie, proste rozwiązanie
Joe
1
Czy można to zrobić, jeśli klucze są liczbami?
Marc Sloth Eastman
25

rozwiązanie jednoprzewodowe

const removeKey = (key, {[key]: _, ...rest}) => rest;
punksta
źródło
4

Jak zasugerowano w komentarzach powyżej, jeśli chcesz to przedłużyć, aby usunąć więcej niż jeden element z Twojego, objectktórego lubię używać filter. ireduce

na przykład

    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
źródło
1
Dlaczego nie zastosujesz warunku filtra w redukcji, aby zaoszczędzić sobie jedną pętlę?
Ivo Sabev
dzięki za wskazówkę @IvoSabev Mam kilka funkcji w moim kodzie, w których filtruję, a następnie zmniejszam, ale rozważę twoją sugestię dotyczącą zachowania pętli.
ak85
4

Aby dodać pikanterii wprowadzając Performance. Sprawdź ten wątek poniżej

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

Użycie operatora delete ma negatywny wpływ na wydajność wzorca klas ukrytych w wersji 8. Ogólnie zaleca się, aby go nie używać.

Alternatywnie, aby usunąć własne wyliczalne właściwości obiektu, moglibyśmy utworzyć nową kopię obiektu bez tych właściwości (na przykład przy użyciu lodash):

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

Lub nawet zdefiniuj wartość właściwości na null lub undefined (która jest niejawnie ignorowana podczas serializacji do JSON):

o.prop = undefined

Możesz też użyć niszczycielskiego sposobu

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

I bardziej praktyczny przykład:

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

Jak widać, możesz użyć [somePropsVarForDynamicName]: scopeVarName w nazwach dynamicznych składni. I możesz umieścić wszystko w nawiasach (nowy blok), aby reszta została po nim zebrana.

Tutaj test: wprowadź opis obrazu tutaj

exec:

wprowadź opis obrazu tutaj

Lub możemy skorzystać z jakiejś funkcji, takiej jak

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

dla maszynopisu

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

Stosowanie:

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

a = deleteProps(a, 'propB'); 

// or 

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

W ten sposób powstaje nowy obiekt. I zachowana jest szybka właściwość obiektu. Co może być ważne lub ważne. Jeśli mapowanie i obiekt będą dostępne wiele razy.

Kojarzenie się undefinedmoże być również dobrym sposobem. Kiedy możesz sobie na to pozwolić. W przypadku kluczy możesz też sprawdzić wartość. Na przykład, aby uzyskać wszystkie aktywne klucze, wykonaj coś takiego:

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.

Niezdefiniowany nie nadaje się jednak na dużą listę. Lub rozwój w czasie z wieloma rekwizytami, które pojawią się. Ponieważ użycie pamięci będzie rosło i nigdy nie zostanie wyczyszczone. Więc to zależy od użycia. A samo stworzenie nowego obiektu wydaje się być dobrym sposobem.

Wtedy Premature optimization is the root of all evilzadziała. Musisz więc być świadomy tego kompromisu. A co jest potrzebne, a co nie.

Uwaga dotycząca _.omit () z lodash

Został usunięty z wersji 5. Nie możesz go znaleźć w repozytorium. I tutaj kwestia, która o tym mówi.

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

v8

Możesz to sprawdzić, co jest dobrym lekturą https://v8.dev/blog/fast-properties

Mohamed Allal
źródło
1

Mój problem z zaakceptowaną odpowiedzią, ze standardu reguł ESLint, jeśli próbujesz zniszczyć:

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

dwie nowe zmienne notNeededi alsoNotNeededmogą generować ostrzeżenie lub błąd w zależności od konfiguracji, ponieważ są teraz nieużywane. Po co więc tworzyć nowe zmienne, jeśli nie są używane?

Myślę, że naprawdę musisz użyć tej deletefunkcji.

Phil Lucks
źródło
4
no-unused-varsPrzepis rzeczywiście ma możliwość ignoreRestSiblingsteraz, aby pokryć ten przypadek użycia: eslint.org/docs/rules/no-unused-vars#ignorerestsiblings
Amann
0

z lodash cloneDeep i delete

(uwaga: klon lodash może być używany zamiast płytkich obiektów)

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
źródło
0

Dla mojego kodu potrzebowałem skróconej wersji dla wartości zwracanej przez map (), ale rozwiązania operacji multiline / mutli były „brzydkie”. Kluczową cechą jest to, void(0)co stare, które postanawiają undefined.

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

Nieruchomość pozostaje w obiekcie:

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

ale struktura transmisji zabija to za mnie (bc stringify):

console.log(JSON.stringify(o2)) // {"firstname":"Jane","age":31}
Jonny
źródło