Como contar com eficiência o número de chaves / propriedades de um objeto em JavaScript?

1545

Qual é a maneira mais rápida de contar o número de chaves / propriedades de um objeto? É possível fazer isso sem iterar sobre o objeto? ou seja, sem fazer

var count = 0;
for (k in myobj) if (myobj.hasOwnProperty(k)) count++;

(O Firefox forneceu uma __count__propriedade mágica , mas isso foi removido em algum momento da versão 4.)

mjs
fonte
3
Relacionados: stackoverflow.com/questions/5223/...
ripper234
2
uma referência de desempenho para diferentes maneiras: jsben.ch/#/oSt2p
EscapeNetscape
1
Possível duplicado do comprimento de um objeto JavaScript
Adam Katz

Respostas:

2518

Para fazer isso em qualquer ambiente compatível com ES5 , como , Chrome, IE 9+, Firefox 4+ ou Safari 5+:

Object.keys(obj).length
Avi Linho
fonte
8
Não apenas Node.js, mas qualquer ambiente que suporta ES5
Yi Jiang
59
BTW ... apenas executei alguns testes ... esse método é executado em O (n) tempo. Um loop for não é muito pior que esse método. ** rosto triste ** stackoverflow.com/questions/7956554/...
BMiner
161
-1 (-200 se eu pudesse) Isso não apenas repete o objeto, mas também cria uma nova matriz com todas as suas chaves; portanto, falha completamente em responder à pergunta.
GetFree,
42
Parece muito mais rápido do que fazer o para (pelo menos no Chrome 25): jsperf.com/count-elements-in-object
fserb
34
@ Get Free Por que tantos polegares para cima? Esta é definitivamente a maneira mais rápida em termos de codificação. Não são necessários métodos ou bibliotecas extras. Em termos de velocidade do código, aparentemente também não é tão ruim. Não é uma falha completa. 87 polegares para cima falham para você.
Andrew
150

Você pode usar este código:

if (!Object.keys) {
    Object.keys = function (obj) {
        var keys = [],
            k;
        for (k in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, k)) {
                keys.push(k);
            }
        }
        return keys;
    };
}

Em seguida, você também pode usar isso em navegadores antigos:

var len = Object.keys(obj).length;
Renaat De Muynck
fonte
2
Qual é o objetivo do cheque (Object.prototype.hasOwnProperty.call(obj, k))?
Styfle 14/05
14
@styfle Se você usar um loop for para iterar as propriedades do objeto, também obterá as propriedades na cadeia de protótipos. É por isso que a verificação hasOwnPropertyé necessária. Ele retorna apenas propriedades definidas no próprio objeto.
Renaat De Muynck
13
@styfle Para simplificar, você pode escrever obj.hasOwnProperty(k)(eu realmente fiz isso no meu post original, mas atualizei mais tarde). hasOwnPropertyestá disponível em todos os objetos porque faz parte do Objectprotótipo do, mas no raro caso de esse método ser removido ou substituído, você poderá obter resultados inesperados. Ao chamá- Object.prototypelo, torna-o um pouco mais robusto. A razão para usar callé porque você deseja chamar o método em objvez de no protótipo.
Renaat De Muynck
6
Não seria melhor usar esta versão? developer.mozilla.org/en-US/docs/JavaScript/Reference/...
Xavier Delamotte
1
@XavierDelamotte Você está absolutamente correto. Enquanto minha versão funciona, é muito básica e mentora como exemplo. O código da Mozilla é mais seguro. (PS: O seu link também está na resposta aceita)
Renaat De Muynck
137

Se você estiver usando o Underscore.js, poderá usar o _.size (obrigado @douwe):
_.size(obj)

Como alternativa, você também pode usar _.keys, o que pode ser mais claro para alguns:
_.keys(obj).length

Eu recomendo o Underscore, é uma biblioteca restrita para fazer muitas coisas básicas. Sempre que possível, eles correspondem ao ECMA5 e adiam para a implementação nativa.

Caso contrário, eu apoio a resposta da @ Avi. Editei-o para adicionar um link ao documento MDC, que inclui o método keys () que você pode adicionar a navegadores não-ECMA5.

studgeek
fonte
9
Se você usar underscore.js, deverá usar _.size. O bom é que, se você mudar de matriz para objeto ou vice-versa, o resultado permanecerá o mesmo.
douwe 20/06
2
E, pelo meu entendimento, o lodash geralmente é melhor que o sublinhado (embora eles façam coisas semelhantes).
Merlyn Morgan-Graham
1
@ MerlynMorgan-Graham, se bem me lembro, lodash é originalmente um fork do sublinhado ...
molson504x
6
_.keys(obj).lengthfuncionou melhor para mim, porque meu objeto de retorno às vezes é uma string simples sem propriedades dentro dele. _.size(obj)dá-me o comprimento da corda, enquanto _.keys(obj).lengthretorna 0.
Jacob Stamm
O (n) complexidade . Lodash e Underscore usam Object.keysinternamente. O sublinhado também copia todas as chaves em uma matriz dentro de um for..inloop, se Object.keysnão estiver definido.
N. Kudryavtsev
82

A implementação padrão de Objeto ( Propriedades e Métodos Internos do Objeto ES5.1 ) não exige que um Objectrastreie seu número de chaves / propriedades; portanto, não deve haver uma maneira padrão de determinar o tamanho de um objeto.Object sem iterar explícita ou implicitamente suas chaves.

Então, aqui estão as alternativas mais usadas:

1. Object.keys do ECMAScript ()

Object.keys(obj).length;Trabalha iterando internamente as chaves para calcular uma matriz temporária e retorna seu comprimento.

  • Prós - sintaxe legível e limpa. Nenhuma biblioteca ou código personalizado necessário, exceto um calço, se o suporte nativo estiver indisponível
  • Contras - sobrecarga de memória devido à criação da matriz.

2. Soluções baseadas em bibliotecas

Muitos exemplos baseados em bibliotecas em outras partes deste tópico são expressões úteis no contexto de sua biblioteca. Do ponto de vista do desempenho, no entanto, não há nada a ganhar em comparação com um código sem biblioteca perfeito, pois todos esses métodos de biblioteca realmente encapsulam um loop for ou ES5 Object.keys(nativo ou shimmed).

3. Otimizando um loop for

A parte mais lenta desse loop for geralmente é a .hasOwnProperty()chamada, devido à sobrecarga da chamada de função. Portanto, quando eu apenas quero o número de entradas de um objeto JSON, apenas pulo a .hasOwnProperty()chamada se souber que nenhum código foi nem será estendido Object.prototype.

Caso contrário, seu código poderá ser otimizado levemente, tornando klocal ( var k) e usando o operador prefix-increment ( ++count) em vez do postfix.

var count = 0;
for (var k in myobj) if (myobj.hasOwnProperty(k)) ++count;

Outra idéia depende do armazenamento em cache do hasOwnPropertymétodo:

var hasOwn = Object.prototype.hasOwnProperty;
var count = 0;
for (var k in myobj) if (hasOwn.call(myobj, k)) ++count;

Se isso é mais rápido ou não em um determinado ambiente, é uma questão de benchmarking. Um ganho de desempenho muito limitado pode ser esperado de qualquer maneira.

Luc125
fonte
Por que var k in myobjaumentar o desempenho? Até onde eu sei, apenas funções declaram novo escopo em JavaScript. Os in-loops são uma exceção a esta regra?
Lars Gyrup Brink Nielsen
1
Isso é mais rápido? for (var k in myobj) hasOwn.call(myobj, k) && ++count;ou seja, substituindo a instrução if por um simples &&?
Hamza Kubba
Última coisa que você pode fazer com Object.getOwnPropertyNames(obj).length:; muito mais simples.
Wilt
29

Se você está realmente enfrentando um problema de desempenho, sugiro agrupar as chamadas que adicionam / removem propriedades ao / do objeto com uma função que também aumenta / diminui uma propriedade nomeada adequadamente (size?).

Você só precisa calcular o número inicial de propriedades uma vez e prosseguir a partir daí. Se não houver um problema real de desempenho, não se preocupe. Apenas envolva esse pedaço de código em uma função getNumberOfProperties(object)e termine com ela.

Confusão
fonte
4
@hitautodestruct Porque ele oferece uma solução.
esmagar
@crush Esta resposta parece sugerir coisas para fazer, em vez de fornecer uma solução direta.
Hitautodestruct
5
@hitautodestruct sugere uma resposta: incrementando / decrementando uma contagem encapsulada com os métodos de adição / remoção. Há outra resposta exatamente como esta abaixo. A única diferença é que o Confusion não ofereceu nenhum código. As respostas não são obrigatórias para fornecer apenas soluções de código.
esmagar
1
ele pode não ser perfeito ... mas em comparação com as outras "respostas" Esta pode ser a melhor solução para algumas situações
d.raev
1
Até agora, essa é a única solução que vejo que é a complexidade do desempenho de tempo constante O (1) e, portanto, é a única solução que responde aos detalhes da pergunta "sem iterar" e deve, portanto, ser a verdadeira resposta aceita. A maioria, senão todas as outras respostas, não responde a isso, porque oferece uma complexidade de desempenho de tempo linear O (n); esse também é o caso das soluções de 1 linha que chamam algo como uma função .keys (), pois essas chamadas de função são O (n).
Cellepo # 30/18
17

Conforme declarado pela Avi Flax https://stackoverflow.com/a/4889658/1047014

Object.keys(obj).length

fará o truque para todas as propriedades enumeráveis ​​do seu objeto, mas também incluirá as propriedades não enumeráveis ​​que você pode usar Object.getOwnPropertyNames. Aqui está a diferença:

var myObject = new Object();

Object.defineProperty(myObject, "nonEnumerableProp", {
  enumerable: false
});
Object.defineProperty(myObject, "enumerableProp", {
  enumerable: true
});

console.log(Object.getOwnPropertyNames(myObject).length); //outputs 2
console.log(Object.keys(myObject).length); //outputs 1

console.log(myObject.hasOwnProperty("nonEnumerableProp")); //outputs true
console.log(myObject.hasOwnProperty("enumerableProp")); //outputs true

console.log("nonEnumerableProp" in myObject); //outputs true
console.log("enumerableProp" in myObject); //outputs true

Conforme declarado aqui, ele tem o mesmo suporte de navegador queObject.keys

No entanto, na maioria dos casos, talvez você não queira incluir os não numeráveis ​​nesse tipo de operação, mas é sempre bom saber a diferença;)

BenderTheOffender
fonte
1
Polegares para cima para mencionar Object.getOwnPropertyNames, você era o único aqui ...
Wilt
15

Não conheço nenhuma maneira de fazer isso, no entanto, para manter as iterações no mínimo, você pode tentar verificar a existência de __count__e, se ela não existir (por exemplo, não o Firefox), poderá iterar sobre o objeto e definir para uso posterior, por exemplo:

if (myobj.__count__ === undefined) {
  myobj.__count__ = ...
}

Dessa forma, qualquer navegador compatível __count__usaria isso, e as iterações seriam realizadas apenas para aqueles que não o fazem. Se a contagem for alterada e você não puder fazer isso, sempre poderá transformá-la em uma função:

if (myobj.__count__ === undefined) {
  myobj.__count__ = function() { return ... }
  myobj.__count__.toString = function() { return this(); }
}

Dessa forma, sempre que você fizer referência a myobj. __count__a função será acionada e recalculada.

Luke Bennett
fonte
13
Note-se que Object.prototype.__count__está a ser removido em gecko 1.9.3: whereswalden.com/2010/04/06/... contagem -property-de-objetos-se-a ser removido-/
dshaw
17
Agora que o Firefox 4 saiu, esta resposta está obsoleta. Object.__count__se foi, e boa viagem também.
Yi Jiang
1
Eu não diria que a resposta é obsoleta. Ainda é uma estratégia interessante encapsular um valor em uma função.
devios1
deve ser utilizando o objecto protótipo para estender
Aaria Carter-Weir
13

Para iterar na resposta do Avi Flax, a resposta Object.keys (obj) .length está correta para um objeto que não possui funções vinculadas a ele

exemplo:

obj = {"lol": "what", owo: "pfft"};
Object.keys(obj).length; // should be 2

versus

arr = [];
obj = {"lol": "what", owo: "pfft"};
obj.omg = function(){
    _.each(obj, function(a){
        arr.push(a);
    });
};
Object.keys(obj).length; // should be 3 because it looks like this 
/* obj === {"lol": "what", owo: "pfft", omg: function(){_.each(obj, function(a){arr.push(a);});}} */

etapas para evitar isso:

  1. não coloque funções em um objeto no qual deseja contar o número de chaves

  2. use um objeto separado ou crie um novo objeto especificamente para funções (se você quiser contar quantas funções existem no arquivo usando Object.keys(obj).length)

Também sim, eu usei o módulo _ ou sublinhado de nodejs no meu exemplo

documentação pode ser encontrada aqui http://underscorejs.org/ , bem como sua fonte no github e várias outras informações

E, finalmente, uma implementação do lodash https://lodash.com/docs#size

_.size(obj)

Belldandu
fonte
Em resposta aos seus comentários sobre Array(obj).length: Não funciona. // JSFiddle.net/Jhy8M/ #
Jamie Chong
sim eu olhei para ele um pouco mais im vai acabar removendo esta resposta, se possível, ou apenas editá-lo todos juntos
Belldandu
Não estou vendo que isso tenha algo a ver com funções, por exemplo, e no Chrome não vejo esse comportamento. Eu suspeitaria que isso possa ter tido a ver com o comportamento padrão de Object.defineProperty (): enumerable, que é false, embora ainda não tenha encontrado nenhuma documentação sobre como var obj = { a: true, b: true }diferir var obj = {}; obj.a = true; obj.b = true;ou simplesmente se uma interpretação / semântica diferente do W3 tiver foi adotado pelo Chrome.
Nolo
10

como respondido acima: Object.keys(obj).length

Mas: como agora temos uma classe de mapa real no ES6, sugiro usá-la em vez de usar as propriedades de um objeto.

const map = new Map();
map.set("key", "value");
map.size; // THE fastest way
Flavien Volken
fonte
8

Para aqueles que têm o Underscore.js incluído no projeto, você pode:

_({a:'', b:''}).size() // => 2

ou estilo funcional:

_.size({a:'', b:''}) // => 2
hakunin
fonte
8

Aqui estão alguns testes de desempenho para três métodos;

https://jsperf.com/get-the-number-of-keys-in-an-object

Object.keys (). Length

20.735 operações por segundo

Muito simples e compatível. É executado rápido, mas caro, porque cria uma nova matriz de chaves, que depois é descartada.

return Object.keys(objectToRead).length;

percorrer as teclas

15.734 operações por segundo

let size=0;
for(let k in objectToRead) {
  size++
}
return size;

Um pouco mais lento, mas nem um pouco próximo do uso da memória, provavelmente melhor se você estiver interessado em otimizar para dispositivos móveis ou outras pequenas máquinas

Usando Mapa em vez de Objeto

953.839.338 operações por segundo

return mapToRead.size;

Basicamente, o Map rastreia seu próprio tamanho, então estamos retornando um campo numérico. Muito, muito mais rápido que qualquer outro método. Se você tiver o controle do objeto, converta-o em mapas.

Steve Cooper
fonte
6

De: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Object.defineProperty (obj, prop, descritor)

Você pode adicioná-lo a todos os seus objetos:

Object.defineProperty(Object.prototype, "length", {
    enumerable: false,
    get: function() {
        return Object.keys(this).length;
    }
});

Ou um único objeto:

var myObj = {};
Object.defineProperty(myObj, "length", {
    enumerable: false,
    get: function() {
        return Object.keys(this).length;
    }
});

Exemplo:

var myObj = {};
myObj.name  = "John Doe";
myObj.email = "[email protected]";
myObj.length; //output: 2

Adicionado dessa maneira, ele não será exibido nos loops for..in:

for(var i in myObj) {
     console.log(i + ":" + myObj[i]);
}

Resultado:

name:John Doe
email:leaked@example.com

Nota: não funciona em navegadores <IE9.

lepe
fonte
Se você deseja estender protótipos embutidos ou preencher uma propriedade (por exemplo, uma correção de macaco), faça-o corretamente: para compatibilidade direta, verifique se a propriedade existe primeiro e torne a propriedade não enumerável para que as próprias chaves de objetos construídos não são poluídos. Para métodos, use métodos reais . Minha recomendação: siga estes exemplos que demonstram como adicionar um método que se comporte o mais próximo possível dos métodos internos.
user4642212
5

Como resolvi esse problema é criar minha própria implementação de uma lista básica que mantém um registro de quantos itens estão armazenados no objeto. É muito simples. Algo assim:

function BasicList()
{
   var items = {};
   this.count = 0;

   this.add = function(index, item)
   {
      items[index] = item;
      this.count++;
   }

   this.remove = function (index)
   {
      delete items[index];
      this.count--;
   }

   this.get = function(index)
   {
      if (undefined === index)
        return items;
      else
        return items[index];
   }
}
Click Voto a favor
fonte
1
Alternativa interessante. Isso elimina a sobrecarga de uma função de contagem agregada, mas ao custo de uma chamada de função toda vez que você adiciona ou remove um elemento, que infelizmente pode ser pior. Pessoalmente, eu usaria essa implementação de lista para o encapsulamento de dados e métodos personalizados que ele pode fornecer em comparação com uma matriz simples, mas não quando eu só preciso da contagem rápida de itens.
Luc125
1
Gosto da sua resposta, mas também sou um lemming e clicou em voto positivo. Isso apresenta um dilema interessante. Você não está respondendo a algum comportamento em suas instruções, como a minha situação em que já votei sua resposta, mas sou instruída a "clicar em votar" e não posso. A instrução falha silenciosamente, mas, a partir do seu conteúdo aqui, entendo que falhar silenciosamente não é algo que você gosta do seu código. Apenas um aviso.
L0j1k
1
Eu realmente gosto desta resposta, boa estrutura de dados. E se houvesse um impacto no desempenho com as chamadas de função ao adicionar, haveria um aumento no desempenho muito maior se fosse necessário iterar sobre um objeto. Isso deve permitir a patten circuito mais rápidovar i = basiclist.count while(i--){...}
Lex
Uma lista básica não deveria incluir pelo menos verificações básicas? Como verificar se addsubstitui um item antigo ou se removeé chamado por um índice inexistente. Além disso, não é possível verificar se a lista tem um determinado índice se undefinedé um valor de item válido.
Robert
2
Uma lista deve ser ordenada e iterável. Os dados são armazenados em um objeto para que não haja garantia na ordem dos elementos. Como você encontra o comprimento de uma lista com orifícios? this.count? O valor mais alto do índice? Se você adicionar dois itens no mesmo índice, a contagem entrará em um estado de erro.
Ultimate Gobblement
4

Você pode usar Object.keys(data).lengthpara encontrar o comprimento do objeto JSON com os principais dados

Codemaker
fonte
3

Para aqueles que possuem o Ext JS 4 em seu projeto, você pode fazer:

Ext.Object.getSize(myobj);

A vantagem disso é que ele funcionará em todos os navegadores compatíveis com Ext (incluindo IE6-IE8); no entanto, acredito que o tempo de execução não seja melhor que O (n), como em outras soluções sugeridas.

Mark Rhodes
fonte
2

Você pode usar:

Object.keys(objectName).length; 

e

Object.values(objectName).length;
Fayaz
fonte
1

O OP não especificou se o objeto é um nodeList; se for, você pode simplesmente usar o método length diretamente. Exemplo:

buttons = document.querySelectorAll('[id=button)) {
console.log('Found ' + buttons.length + ' on the screen'); 
Robert Sinclair
fonte
0

Se o jQuery acima não funcionar, tente

$(Object.Item).length
codejoecode
fonte
3
Parece que Object.Itemnão existe
Luc125
-1

Eu não acho que isso seja possível (pelo menos não sem o uso de alguns componentes internos). E eu não acho que você ganharia muito otimizando isso.

uma mistura
fonte
2
A resposta aceita mostra que isso pode ser feito e você não tem contexto para afirmar que não há nada a ganhar.
Conduíte
-1

Eu tento disponibilizá-lo para todos os objetos como este:

Object.defineProperty(Object.prototype, "length", {
get() {
    if (!Object.keys) {
        Object.keys = function (obj) {
            var keys = [],k;
            for (k in obj) {
                if (Object.prototype.hasOwnProperty.call(obj, k)) {
                    keys.push(k);
                }
            }
            return keys;
        };
    }
    return Object.keys(this).length;
},});

console.log({"Name":"Joe","Age":26}.length) //returns 2
Taquatech
fonte