Como limpar rapidamente um objeto JavaScript?

170

Com uma matriz JavaScript, posso redefini-la para um estado vazio com uma única atribuição:

array.length = 0;

Isso faz com que o Array "pareça" vazio e pronto para reutilização, e até onde eu entendo é uma "operação" única - ou seja, tempo constante.

Existe uma maneira semelhante de limpar um objeto JS? Eu sei que posso iterar seus campos excluindo-os:

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

mas isso tem complexidade linear.

Também posso jogar o objeto fora e criar um novo:

obj = {};

Mas a criação "promíscuo" de novos objetos leva a problemas com a Coleta de Lixo no IE6. ( Como descrito aqui )

levik
fonte
2
"array.length == 0 ... é uma única 'operação' - ou seja, tempo constante" - duvido disso.
Miles
1
Eu não acredito que ele apague qualquer conteúdo - apenas faça coisas como push () funcionarem como se a matriz estivesse vazia. Você tem uma referência ao contrário sendo verdadeiro?
levik
2
@derobert: Isso é um pouco presunçoso. O problema da coleta de lixo do IE6 está bem documentado.
levik
O código na postagem original está incorreto. O loop interno deve ser: delete obj [prop]. Veja minha postagem stackoverflow.com/questions/6780315/…
stackoverflowuser2010
6
para qualquer um usando esse trecho, o usofor (var prop in obj)
bendytree

Respostas:

54

A resposta curta para sua pergunta, eu acho, é não (você pode apenas criar um novo objeto).

  1. Neste exemplo, acredito que definir o comprimento como 0 ainda deixa todos os elementos para coleta de lixo.

  2. Você pode adicionar isso ao Object.prototype se for algo que você usaria com frequência. Sim, é linear em complexidade, mas qualquer coisa que não faça coleta de lixo mais tarde será.

  3. essa é a melhor solução. Sei que não está relacionado à sua pergunta - mas por quanto tempo precisamos continuar apoiando o IE6? Existem muitas campanhas para interromper o uso dela.

Fique à vontade para me corrigir se houver algo incorreto acima.

jthompson
fonte
4
Algumas empresas precisam dar suporte ao IE6 por uma questão de política - e, enquanto desfruta de uma participação de mercado de dois dígitos. O problema do GC do IE não é que as coisas não sejam coletadas, é que a coleção executa todas as alocações do X e leva mais tempo a cada vez. Daí a necessidade de reutilizar objetos.
levik
Sim, existem muitos casos em que ainda é usado devido à política da empresa / etc. Eu estava fora da classificação do tópico por maldade :) Então, como excluir obj.prop; executar quando a própria propriedade é um objeto? Não sei se você ganha muita eficiência por lá.
22610 jthompson
2
GC ruim significa que o IE6 será mais lento, o que significa que há ainda mais incentivo para atualizar. Você ainda está apoiando, é só que vai correr devagar.
#
1
Isso significa que seu aplicativo apresenta um desempenho ruim para uma parte do seu público-alvo. Algumas pessoas não têm a opção de atualizar porque estão em um ambiente de TI controlado. Talvez a empresa deles use um controle Active-X que funcione apenas com o IE6.
levik 26/03/09
2
O @levik simplesmente não trabalha para uma empresa que o força a dar suporte ao IE6. isso não pode ser uma boa companhia de qualquer maneira.
low_rents
121

Bem, correndo o risco de facilitar as coisas ...

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

... parece ser bastante eficaz na limpeza do objeto em uma linha de código com um mínimo de colchetes assustadores. Todos os membros serão realmente excluídos em vez de deixados como lixo.

Obviamente, se você deseja excluir o objeto em si, ainda precisará fazer um delete () separado para isso.

Wytze
fonte
2
A exclusão apenas quebra a referência, por exemplo, faz com que myObject [member] avalie como indefinido e tecnicamente deixa o objeto como lixo, a menos que exista outra referência ao objeto.
Joey Carson
Esta resposta é muito responsável. Se aplicado a todos os objetos gerenciados em uma fase de descarte, geralmente cuidará de muitas cadeias acidentais de objetos. rock on Wytze
deepelement
1
O OP sugere o uso hasOwnPropertynesse loop. Eu li os documentos, mas ainda estou tentando entender quais são os riscos, se houver, se omitirmos o hasOwnProperty()cheque.
logidelic
2
Eu pensei que era perigoso excluir propriedades enquanto iterava sobre o objeto - na maioria dos idiomas, isso invalidaria o iterador e quebraria as coisas, sutilmente ou catastroficamente - mas, aparentemente, isso é bom no JavaScript pelo MDN: "Uma propriedade que foi excluída antes foi visitado não será visitado mais tarde ". developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Piotr
46

ES5

A solução ES5 pode ser:

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

ES6

E a solução ES6 pode ser:

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

atuação

Independentemente das especificações, as soluções mais rápidas serão geralmente:

// 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];
}

A razão pela qual for..indeve ser realizada apenas em objetos rasos ou simples é que ela percorre as propriedades herdadas prototipicamente, não apenas as propriedades próprias que podem ser excluídas. No caso, não se sabe ao certo que um objeto é simples e propriedades são enumeráveis, forcom Object.getOwnPropertyNamesé uma escolha melhor.

Estus Flask
fonte
7

Você pode tentar isso. A função abaixo define todos os valores das propriedades do objeto como indefinidos. Funciona bem com objetos aninhados.

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
fonte
Observe que os campos estão sendo definidos apenas como indefinidos. Isso causará vazamentos de memória ao longo do tempo, pois os campos não serão limpos pelo coletor de lixo.
Alexis Tyler #
7

Então, para recapitular sua pergunta: você deseja evitar, tanto quanto possível, problemas com o bug do IE6 GC. Esse bug tem duas causas:

  1. A coleta de lixo ocorre uma vez a cada tantas alocações ; portanto, quanto mais alocações você fizer, mais frequentemente o GC será executado;
  2. Quanto mais objetos você tiver 'no ar', mais tempo cada corrida de Coleta de Lixo demora (pois ela percorre toda a lista de objetos para ver quais estão marcados como lixo).

A solução para causar 1 parece ser: mantenha o número de alocações baixo; atribua novos objetos e cadeias o mínimo possível.

A solução para causar 2 parece ser: mantenha o número de objetos "vivos" baixos; exclua suas strings e objetos assim que não precisar mais deles e crie-os novamente quando necessário.

Até certo ponto, essas soluções são contraditórias: manter baixo o número de objetos na memória exigirá mais alocações e desalocações. Por outro lado, reutilizar constantemente os mesmos objetos pode significar manter mais objetos na memória do que o estritamente necessário.


Agora a sua pergunta. Se você redefinirá um objeto criando um novo ou excluindo todas as suas propriedades: isso dependerá do que você deseja fazer com ele posteriormente.

Você provavelmente desejará atribuir novas propriedades a ele:

  • Se você fizer isso imediatamente, sugiro atribuir as novas propriedades imediatamente e pular a exclusão ou a limpeza primeiro. (Certifique-se de que todas as propriedades sejam substituídas ou excluídas!)
  • Se o objeto não for usado imediatamente, mas será repovoado em algum estágio posterior, sugiro excluí-lo ou atribuí-lo nulo e criar um novo posteriormente.

Não existe uma maneira rápida e fácil de limpar um objeto JScript para reutilização como se fosse um novo objeto - sem criar um novo. O que significa que a resposta curta para sua pergunta é 'Não', como jthompson diz.

Martijn
fonte
4

Algo novo para se pensar em aguardar Object.observe no ES7 e com a ligação de dados em geral. Considerar:

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()

Portanto, nessa circunstância 2, pode ser a melhor escolha.

Terry Thorsen
fonte
Se você usar estruturas como AngularJS, que podem vincular objetos a exibições (ligação de uma ou duas vias), limpar o objeto com obj = {}fará com que a estrutura não tenha conhecimento de qualquer alteração adicional no objeto, portanto, seus modelos não serão renderizados corretamente. A opção 2, no entanto, funcionará corretamente.
Ivan Hušnjak 02/03
Bom ponto. Preciso manter o mesmo objeto de heap, e isso demonstra outro motivo pelo qual você deseja manter a mesma referência.
Cody
1

Você pode excluir os objetos, mas não as variáveis. delete abc;é inválido no ES5 (e lança com o uso strict).

Você pode atribuí-lo a null para configurá-lo para exclusão no GC (isso não acontecerá se você tiver outras referências a propriedades)

Definir lengthpropriedade em um objeto não altera nada. (apenas define a propriedade)

Ven
fonte
Para ser claro, ao definir length0 em uma matriz (que é um tipo específico de objeto - se você não acredita em mim, tente executar typeof []), ela não apenas definirá a propriedade, como também limpará o conteúdo da matriz . Veja stackoverflow.com/questions/1232040/…
Sean the Bean
Bem, você pode dizer excluir window.abcporque é um suporte nesse caso.
shaedrich 31/07
0

Isso me incomodou por muito tempo, então aqui está minha versão, pois eu não queria um objeto vazio, queria um com todas as propriedades, mas redefinii para algum valor padrão. Como uma nova instanciação de uma classe.

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
fonte