Jak szybko wyczyścić obiekt JavaScript?

170

Za pomocą tablicy JavaScript mogę zresetować ją do pustego stanu za pomocą jednego przypisania:

array.length = 0;

To sprawia, że ​​tablica „wydaje się” pusta i gotowa do ponownego użycia, a o ile rozumiem, jest to pojedyncza „operacja” - to znaczy stały czas.

Czy istnieje podobny sposób na wyczyszczenie obiektu JS? Wiem, że mogę iterować jego pola, usuwając je:

for (var prop in obj) { if (obj.hasOwnProperty(prop)) { delete obj[prop]; } }

ale ma to złożoność liniową.

Mogę też po prostu wyrzucić obiekt i stworzyć nowy:

obj = {};

Ale „rozwiązłe” tworzenie nowych obiektów prowadzi do problemów z Garbage Collection w IE6. ( Jak opisano tutaj )

levik
źródło
2
"array.length == 0 ... jest pojedynczą 'operacją' - czyli stałym czasem" - wątpię w to.
Miles
1
Nie wierzę, że usuwa jakąkolwiek zawartość - po prostu sprawia, że ​​rzeczy takie jak push () działają tak, jakby tablica była pusta. Czy masz odniesienie do tego, że jest odwrotnie?
levik
2
@derobert: To trochę aroganckie. Problem z usuwaniem śmieci w IE6 jest dobrze udokumentowany.
levik
Kod w oryginalnym poście jest nieprawidłowy. Pętla wewnętrzna powinna wyglądać następująco: delete obj [prop]. Zobacz moje publikowanie stackoverflow.com/questions/6780315/ ...
stackoverflowuser2010
6
dla każdego, kto używa tego fragmentu, użyjfor (var prop in obj)
bendytree

Odpowiedzi:

54

Myślę, że krótka odpowiedź na twoje pytanie brzmi: nie (możesz po prostu stworzyć nowy obiekt).

  1. W tym przykładzie uważam, że ustawienie długości na 0 nadal pozostawia wszystkie elementy do czyszczenia pamięci.

  2. Możesz dodać to do Object.prototype, jeśli jest to coś, czego często używasz. Tak, jest to liniowe pod względem złożoności, ale wszystko, co później nie usunie elementów bezużytecznych, będzie.

  3. To najlepsze rozwiązanie. Wiem, że to nie jest związane z twoim pytaniem - ale jak długo musimy kontynuować obsługę IE6? Istnieje wiele kampanii mających na celu zaprzestanie korzystania z niego.

Zapraszam do poprawiania mnie, jeśli powyżej jest coś nieprawidłowego.

jthompson
źródło
4
Niektóre firmy muszą wspierać IE6 z powodów politycznych - i to zrobią, chociaż ma dwucyfrowy udział w rynku. Problemem IE GC nie jest to, że rzeczy pozostają niezebrane, tylko to, że kolekcja uruchamia się co alokacje X i za każdym razem trwa dłużej. Stąd potrzeba ponownego wykorzystania obiektów.
levik
Tak, jest wiele przypadków, w których jest nadal używany ze względu na politykę firmy / itp. Byłem poza tematem z powodu złośliwości :) Jak więc skasować obj.prop; działać, gdy własność sama jest przedmiotem? Nie wiem, czy uzyskasz tam dużą efektywność.
jthompson
2
słaby GC oznacza, że ​​IE6 będzie działał wolniej, co oznacza, że ​​jest jeszcze większa zachęta do aktualizacji. Nadal to wspierasz, po prostu będzie działać wolno.
nickf
1
Oznacza to, że Twoja aplikacja działa słabo dla części docelowych odbiorców. Niektóre osoby nie mają możliwości aktualizacji, ponieważ znajdują się w kontrolowanym środowisku IT. Być może ich firma używa formantu Active-X, który działa tylko z IE6.
levik
2
@levik po prostu nie działa dla firmy, która zmusza cię do wspierania IE6. to i tak nie może być dobra firma.
low_rents
121

Cóż, ryzykując, że wszystko będzie zbyt łatwe ...

for (var member in myObject) delete myObject[member];

... wydawałby się całkiem skuteczny w czyszczeniu obiektu w jednej linii kodu z minimalną ilością przerażających nawiasów. Wszyscy członkowie zostaną naprawdę usunięci, a nie pozostawieni jako śmieci.

Oczywiście, jeśli chcesz usunąć sam obiekt, nadal będziesz musiał wykonać w tym celu oddzielną metodę delete ().

Wytze
źródło
2
Usunięcie tylko przerywa odniesienie, np. Powoduje, że myObject [element członkowski] jest oceniany jako niezdefiniowany, a technicznie rzecz biorąc, pozostawia obiekt jako śmieci, chyba że istnieje inne odniesienie do obiektu.
Joey Carson
Ta odpowiedź jest bardzo odpowiedzialna. Jeśli zostanie zastosowana do wszystkich zarządzanych obiektów w fazie usuwania, na ogół zajmie się wieloma przypadkowymi łańcuchami obiektów. rock on Wytze
deepelement
1
OP sugeruje użycie hasOwnPropertyw tej pętli. Przeczytałem dokumentację, ale nadal próbuję się zastanowić, jakie, jeśli w ogóle, są zagrożenia, jeśli pominiemy hasOwnProperty()czek?
logidelic
2
Wydawało mi się, że usuwanie właściwości podczas iteracji obiektu jest niebezpieczne - w większości języków mogłoby to spowodować unieważnienie iteratora i zepsuć rzeczy, subtelnie lub katastrofalnie - ale najwyraźniej jest to w porządku w JavaScript na MDN: „Właściwość, która została wcześniej usunięta został odwiedzony, nie będzie odwiedzany później. " developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Piotr
46

ES5

Rozwiązanie ES5 może być:

// for enumerable and non-enumerable properties
Object.getOwnPropertyNames(obj).forEach(function (prop) {
  delete obj[prop];
});

ES6

A rozwiązaniem ES6 może być:

// for enumerable and non-enumerable properties
for (const prop of Object.getOwnPropertyNames(obj)) {
  delete obj[prop];
}

Występ

Niezależnie od specyfikacji najszybszymi rozwiązaniami będą na ogół:

// for enumerable and non-enumerable of an object with proto chain
var props = Object.getOwnPropertyNames(obj);
for (var i = 0; i < props.length; i++) {
  delete obj[props[i]];
}

// for enumerable properties of shallow/plain object
for (var key in obj) {
  // this check can be safely omitted in modern JS engines
  // if (obj.hasOwnProperty(key))
    delete obj[key];
}

Powodem, dla którego for..inpowinno być wykonywane tylko na płytkim lub prostym obiekcie, jest to, że przechodzi on przez właściwości, które są prototypowo dziedziczone, a nie tylko przez własne właściwości, które można usunąć. W przypadku, gdy nie wiadomo na pewno, że obiekt jest jasny i właściwości są przeliczalne, forze Object.getOwnPropertyNamesjest lepszym wyborem.

Estus Flask
źródło
7

Możesz tego spróbować. Poniższa funkcja ustawia wszystkie wartości właściwości obiektu na niezdefiniowane. Działa również z obiektami zagnieżdżonymi.

var clearObjectValues = (objToClear) => {
    Object.keys(objToClear).forEach((param) => {
        if ( (objToClear[param]).toString() === "[object Object]" ) {
            clearObjectValues(objToClear[param]);
        } else {
            objToClear[param] = undefined;
        }
    })
    return objToClear;
};
Alina Poluykova
źródło
Uwaga, ponieważ pola są ustawiane tylko jako niezdefiniowane, spowoduje to z czasem wycieki pamięci, ponieważ pola nie zostaną wyczyszczone przez moduł odśmiecania pamięci.
Alexis Tyler
7

Podsumowując swoje pytanie: chcesz uniknąć, w miarę możliwości, problemów z błędem IE6 GC. Ten błąd ma dwie przyczyny:

  1. Wyrzucanie elementów bezużytecznych odbywa się raz na wiele alokacji ; w związku z tym im więcej alokacji dokonasz, tym częściej GC będzie działać;
  2. Im więcej obiektów masz „w powietrzu”, tym więcej czasu zajmuje każdy przebieg procesu Garbage Collection (ponieważ będzie przeszukiwał całą listę obiektów, aby zobaczyć, które są oznaczone jako śmieci).

Wydaje się, że rozwiązaniem przyczyny 1 jest: utrzymanie małej liczby przydziałów; przypisuj nowe obiekty i ciągi tak mało, jak to możliwe.

Wydaje się, że rozwiązaniem przyczyny 2 jest: ograniczenie liczby „żywych” obiektów; usuwaj ciągi i obiekty, gdy nie będziesz ich już potrzebować, i twórz je od nowa, gdy zajdzie taka potrzeba.

Do pewnego stopnia rozwiązania te są sprzeczne: utrzymanie małej liczby obiektów w pamięci pociągnie za sobą więcej alokacji i cofnięć. I odwrotnie, ciągłe ponowne wykorzystywanie tych samych obiektów może oznaczać przechowywanie w pamięci większej liczby obiektów, niż jest to absolutnie konieczne.


A teraz pytanie. Niezależnie od tego, czy zresetujesz obiekt, tworząc nowy, czy usuwając wszystkie jego właściwości: będzie to zależeć od tego, co chcesz z nim później zrobić.

Prawdopodobnie będziesz chciał przypisać do niego nowe właściwości:

  • Jeśli zrobisz to natychmiast, sugeruję natychmiastowe przypisanie nowych właściwości i najpierw pomiń usuwanie lub czyszczenie. (Upewnij się jednak, że wszystkie właściwości zostały nadpisane lub usunięte!)
  • Jeśli obiekt nie będzie używany od razu, ale zostanie ponownie zapełniony na późniejszym etapie, proponuję usunąć go lub przypisać mu wartość null, a później utworzyć nowy.

Nie ma szybkiego i łatwego w użyciu sposobu na wyczyszczenie obiektu JScript do ponownego użycia, tak jakby był to nowy obiekt - bez tworzenia nowego. Co oznacza, że ​​krótka odpowiedź na twoje pytanie brzmi „nie”, jak mówi jthompson.

Martijn
źródło
4

Coś nowego do przemyślenia w oczekiwaniu na Object.observe w ES7 i ogólnie z wiązaniem danych. Rozważać:

var foo={
   name: "hello"
};

Object.observe(foo, function(){alert('modified');}); // bind to foo

foo={}; // You are no longer bound to foo but to an orphaned version of it
foo.name="there"; // This change will be missed by Object.observe()

W tej sytuacji nr 2 może być najlepszym wyborem.

Terry Thorsen
źródło
Jeśli używasz frameworków, takich jak AngularJS, które mogą wiązać obiekty z widokami (jedno lub dwukierunkowe wiązanie), wyczyszczenie obiektu za pomocą obj = {}spowoduje, że framework nie będzie świadomy jakichkolwiek dalszych zmian w obiekcie, dlatego twoje szablony nie będą poprawnie renderowane. Jednak opcja nr 2 będzie działać poprawnie.
Ivan Hušnjak
Słuszna uwaga. Muszę zachować ten sam obiekt sterty, a to pokazuje inny powód, dla którego chciałbyś zachować to samo odniesienie.
Cody
1

Możesz usunąć właściwości, ale nie usuwaj zmiennych. delete abc;jest nieprawidłowy w ES5 (i wyrzuca z użyciem ścisłego).

Możesz przypisać go do wartości null, aby ustawić go do usunięcia w GC (nie będzie, jeśli masz inne odniesienia do właściwości)

Ustawienie lengthwłaściwości obiektu niczego nie zmienia. (tylko, cóż, ustawia właściwość)

Ven
źródło
Żeby było jasne, ustawiając lengthna 0 w tablicy (która jest określonym typem obiektu - jeśli mi nie wierzysz, spróbuj uruchomić typeof []), nie tylko ustawi właściwość, w rzeczywistości wyczyści zawartość tablicy . Zobacz stackoverflow.com/questions/1232040/…
Sean the Bean
Możesz powiedzieć usuń, window.abcponieważ w tym przypadku jest to rekwizyt.
shaedrich
0

To mnie niepokoiło przez wieki, więc oto moja wersja, ponieważ nie chciałem pustego obiektu, chciałem mieć taki, który ma wszystkie właściwości, ale został zresetowany do jakiejś domyślnej wartości. Coś jak nowa instancja klasy.

let object1 = {
  a: 'somestring',
  b: 42,
  c: true,
  d:{
    e:1,
    f:2,
    g:true,
    h:{
      i:"hello"
    }
  },
  j: [1,2,3],
  k: ["foo", "bar"],
  l:["foo",1,true],
  m:[{n:10, o:"food", p:true }, {n:11, o:"foog", p:true }],
  q:null,
  r:undefined
};

let boolDefault = false;
let stringDefault = "";
let numberDefault = 0;

console.log(object1);
//document.write("<pre>");
//document.write(JSON.stringify(object1))
//document.write("<hr />");
cleanObject(object1);
console.log(object1);
//document.write(JSON.stringify(object1));
//document.write("</pre>");

function cleanObject(o) {
  for (let [key, value] of Object.entries(o)) {
    let propType = typeof(o[key]);

    //console.log(key, value, propType);

    switch (propType) {
      case "number" :
        o[key] = numberDefault;
        break;

      case "string":
        o[key] = stringDefault;
        break;

      case "boolean":
        o[key] = boolDefault;    
        break;

      case "undefined":
        o[key] = undefined;   
        break;

      default:
        if(value === null) {
            continue;
        }

        cleanObject(o[key]);
        break;
    }
  }
}

// EXPECTED OUTPUT
// Object { a: "somestring", b: 42, c: true, d: Object { e: 1, f: 2, g: true, h: Object { i: "hello" } }, j: Array [1, 2, 3], k: Array ["foo", "bar"], l: Array ["foo", 1, true], m: Array [Object { n: 10, o: "food", p: true }, Object { n: 11, o: "foog", p: true }], q: null, r: undefined }
// Object { a: "", b: 0, c: undefined, d: Object { e: 0, f: 0, g: undefined, h: Object { i: "" } }, j: Array [0, 0, 0], k: Array ["", ""], l: Array ["", 0, undefined], m: Array [Object { n: 0, o: "", p: undefined }, Object { n: 0, o: "", p: undefined }], q: null, r: undefined }

ozzy432836
źródło