Comment compter efficacement le nombre de clés / propriétés d'un objet en JavaScript?

1545

Quelle est la manière la plus rapide de compter le nombre de clés / propriétés d'un objet? Est-il possible de le faire sans itérer sur l'objet? c'est à dire sans faire

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

(Firefox a fourni une __count__propriété magique , mais cela a été supprimé quelque part autour de la version 4.)

mjs
la source
2
une référence de performance pour différentes manières: jsben.ch/#/oSt2p
EscapeNetscape
1
Duplication possible de la longueur d'un objet JavaScript
Adam Katz

Réponses:

2519

Pour ce faire, dans n'importe quel environnement compatible ES5 , tel que Node , Chrome, IE 9+, Firefox 4+ ou Safari 5+:

Object.keys(obj).length
Avi Flax
la source
8
Pas seulement Node.js, mais tout environnement prenant en charge ES5
Yi Jiang
59
BTW ... vient de lancer quelques tests ... cette méthode s'exécute en temps O (n). Une boucle for n'est pas bien pire que cette méthode. ** visage triste ** stackoverflow.com/questions/7956554/…
BMiner
161
-1 (-200 si je pouvais) Cela non seulement itère à travers l'objet mais crée également un tout nouveau tableau avec toutes ses clés, donc il échoue complètement à répondre à la question.
GetFree
42
Cela semble beaucoup plus rapide que de faire le pour (au moins sur Chrome 25): jsperf.com/count-elements-in-object
fserb
34
@GetFree Pourquoi tant de pouces vers le haut? C'est certainement le moyen le plus rapide en termes de codage. Aucune méthode ou bibliothèque supplémentaire n'est requise. En termes de vitesse de code, ce n'est apparemment pas trop mal non plus. Pas un échec complet du tout. 87 pouces vers le haut échoue pour vous.
Andrew
150

Vous pouvez utiliser ce code:

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

Ensuite, vous pouvez également l'utiliser dans les anciens navigateurs:

var len = Object.keys(obj).length;
Renaat De Muynck
la source
2
Quel est le but du chèque (Object.prototype.hasOwnProperty.call(obj, k))?
styfle
14
@styfle Si vous utilisez une boucle for pour parcourir les propriétés de l'objet, vous obtenez également les propriétés dans la chaîne de prototypes. C'est pourquoi la vérification hasOwnPropertyest nécessaire. Il renvoie uniquement les propriétés définies sur l'objet lui-même.
Renaat De Muynck
13
@styfle Pour simplifier, vous pouvez simplement écrire obj.hasOwnProperty(k)(je l'ai fait dans mon article d'origine, mais je l'ai mis à jour plus tard). hasOwnPropertyest disponible sur chaque objet car il fait partie du Objectprototype de, mais dans le cas rare où cette méthode serait supprimée ou remplacée, vous pourriez obtenir des résultats inattendus. En l'appelant depuis, Object.prototypeil le rend peu plus robuste. La raison de l'utilisation callest que vous souhaitez invoquer la méthode au objlieu du prototype.
Renaat De Muynck
6
Ne serait-il pas préférable d'utiliser cette version? developer.mozilla.org/en-US/docs/JavaScript/Reference/…
Xavier Delamotte
1
@XavierDelamotte Vous avez absolument raison. Bien que ma version fonctionne, elle est très basique et mentionne comme exemple. Le code de Mozilla est plus sûr. (PS: Votre lien figure également dans la réponse acceptée)
Renaat De Muynck
137

Si vous utilisez Underscore.js, vous pouvez utiliser _.size (merci @douwe):
_.size(obj)

Alternativement, vous pouvez également utiliser _.keys qui pourrait être plus clair pour certains:
_.keys(obj).length

Je recommande fortement Underscore, sa bibliothèque serrée pour faire beaucoup de choses de base. Dans la mesure du possible, ils correspondent à ECMA5 et s'en remettent à l'implémentation native.

Sinon, je soutiens la réponse de @ Avi. Je l'ai édité pour ajouter un lien vers le document MDC qui inclut la méthode keys () que vous pouvez ajouter aux navigateurs non ECMA5.

studgeek
la source
9
Si vous utilisez underscore.js, vous devez plutôt utiliser _.size. La bonne chose est que si vous passez d'une manière ou d'une autre d'un tableau à un objet ou vice versa, le résultat reste le même.
douwe
2
Et d'après ma compréhension, le lodash est généralement meilleur que le soulignement (bien qu'ils fassent des choses similaires).
Merlyn Morgan-Graham
1
@ MerlynMorgan-Graham si je me souviens bien, lodash est à l'origine une fourchette de soulignement ...
molson504x
6
_.keys(obj).lengtha fonctionné le mieux pour moi, car mon objet de retour est parfois une chaîne simple sans propriétés. _.size(obj)me rend la longueur de la chaîne, tandis que _.keys(obj).lengthrenvoie 0.
Jacob Stamm
O (n) complexité . Lodash et Underscore utilisent en Object.keysinterne. Underscore copie également chaque clé dans un tableau à l'intérieur d'une for..inboucle si elle Object.keysn'est pas définie.
N.Kudryavtsev
82

L'implémentation d'objet standard ( propriétés et méthodes internes de l'objet ES5.1 ) ne nécessite pas Objectde suivi de son nombre de clés / propriétés, il ne devrait donc pas y avoir de méthode standard pour déterminer la taille d'unObject sans itération explicite ou implicite sur ses clés.

Voici donc les alternatives les plus utilisées:

1. Object.keys d'ECMAScript ()

Object.keys(obj).length;Fonctionne en itérant en interne sur les clés pour calculer un tableau temporaire et renvoie sa longueur.

  • Avantages - Syntaxe lisible et propre. Aucune bibliothèque ou code personnalisé requis sauf un shim si le support natif n'est pas disponible
  • Inconvénients - surcharge de mémoire due à la création de la baie.

2. Solutions basées sur la bibliothèque

De nombreux exemples basés sur des bibliothèques ailleurs dans cette rubrique sont des idiomes utiles dans le contexte de leur bibliothèque. Du point de vue des performances, cependant, il n'y a rien à gagner par rapport à un code parfait sans bibliothèque, car toutes ces méthodes de bibliothèque encapsulent en fait une boucle for ou ES5Object.keys For (native ou shimmed).

3. Optimiser une boucle for

La partie la plus lente d'une telle boucle for est généralement l' .hasOwnProperty()appel, en raison de la surcharge d'appel de la fonction. Donc, quand je veux juste le nombre d'entrées d'un objet JSON, je saute juste l' .hasOwnProperty()appel si je sais qu'aucun code n'a fait ni ne s'étendra Object.prototype .

Sinon, votre code pourrait être très légèrement optimisé en rendant klocal ( var k) et en utilisant l'opérateur prefix-increment ( ++count) au lieu de postfix.

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

Une autre idée repose sur la mise en cache de la hasOwnPropertyméthode:

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

Que ce soit plus rapide ou non dans un environnement donné est une question de benchmarking. Un gain de performances très limité peut être attendu de toute façon.

Luc125
la source
Pourquoi var k in myobjaugmenterait les performances? Pour autant que je sache, seules les fonctions déclarent une nouvelle portée en JavaScript. Les boucles entrantes sont-elles une exception à cette règle?
Lars Gyrup Brink Nielsen
1
Est-ce plus rapide? for (var k in myobj) hasOwn.call(myobj, k) && ++count;c'est-à-dire remplacer l'instruction if par un simple &&?
Hamza Kubba du
Dernière chose que vous pouvez faire avec Object.getOwnPropertyNames(obj).length:; beaucoup plus simple.
Flétrissement le
29

Si vous rencontrez réellement un problème de performances, je suggérerais d'encapsuler les appels qui ajoutent / suppriment des propriétés à / de l'objet avec une fonction qui incrémente / décrémente également une propriété (taille?) Correctement nommée.

Vous n'avez qu'à calculer une fois le nombre initial de propriétés et à partir de là. S'il n'y a pas de réel problème de performances, ne vous embêtez pas. Enveloppez simplement ce morceau de code dans une fonction getNumberOfProperties(object)et terminez avec.

Confusion
la source
4
@hitautodestruct Parce qu'il propose une solution.
écraser le
@crush Cette réponse semble suggérer des choses à faire plutôt que de donner une solution directe.
hitautodestruct
5
@hitautodestruct propose une réponse: incrémenter / décrémenter un comptage encapsulé avec les méthodes add / remove. Il y a une autre réponse exactement comme celle-ci ci-dessous. La seule différence est que Confusion n'a proposé aucun code. Les réponses ne sont pas obligées de fournir uniquement des solutions de code.
écraser le
1
ce n'est peut-être pas parfait ... mais par rapport aux autres "réponses", cela peut être la meilleure solution pour certaines situations
d.raev
1
Jusqu'à présent, c'est la seule solution que je vois qui est la complexité de performance à temps constant O (1), et est donc la seule solution qui répond au détail de la question "sans itération" et devrait donc être la réponse tru acceptée. La plupart sinon toutes les autres réponses ne répondent pas à cela, car elles offrent une complexité de performance temporelle O (n) linéaire; c'est également le cas pour les solutions à 1 ligne qui appellent quelque chose comme une fonction .keys (), car ces appels de fonction sont O (n).
cellepo
17

Comme indiqué par Avi Flax https://stackoverflow.com/a/4889658/1047014

Object.keys(obj).length

fera l'affaire pour toutes les propriétés énumérables de votre objet, mais pour inclure également les propriétés non énumérables, vous pouvez utiliser à la place le Object.getOwnPropertyNames. Voici la différence:

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

Comme indiqué ici, il a le même support de navigateur queObject.keys

Cependant, dans la plupart des cas, vous ne voudrez peut-être pas inclure les non-énumérables dans ce type d'opérations, mais il est toujours bon de connaître la différence;)

BenderTheOffender
la source
1
Bravo pour mentionner Object.getOwnPropertyNames, vous étiez le seul ici ...
Flétrissement le
15

Je ne connais aucun moyen de le faire, mais pour garder les itérations au minimum, vous pouvez essayer de vérifier l'existence de __count__et si elle n'existe pas (c'est-à-dire pas Firefox), vous pouvez parcourir l''objet et définir pour une utilisation ultérieure, par exemple:

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

De cette façon, tout navigateur prenant en charge __count__utiliserait cela, et les itérations ne seraient effectuées que pour ceux qui ne le font pas. Si le nombre change et que vous ne pouvez pas le faire, vous pouvez toujours en faire une fonction:

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

De cette façon, chaque fois que vous faites référence à myobj. __count__la fonction se déclenche et recalcule.

Luke Bennett
la source
13
Notez que en Object.prototype.__count__cours de suppression dans Gecko 1.9.3: whereswalden.com/2010/04/06/... nombre -property-de-objets est-être grattées /
dshaw
17
Maintenant que Firefox 4 est sorti, cette réponse est désormais obsolète. Object.__count__est parti, et bon débarras aussi.
Yi Jiang
1
Je ne dirais pas que la réponse est obsolète. C'est toujours une stratégie intéressante pour encapsuler une valeur dans une fonction.
devios1
devrait utiliser l'objet prototype pour étendre
Aaria Carter-Weir
13

Pour itérer sur Avi Flax, répondez Object.keys (obj) .length est correct pour un objet qui n'a pas de fonctions liées à lui

exemple:

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

contre

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);});}} */

étapes pour éviter cela:

  1. ne placez pas de fonctions dans un objet dans lequel vous souhaitez compter le nombre de clés

  2. utiliser un objet séparé ou créer un nouvel objet spécifiquement pour les fonctions (si vous voulez compter le nombre de fonctions dans le fichier en utilisant Object.keys(obj).length)

aussi oui j'ai utilisé le module _ ou underscore de nodejs dans mon exemple

la documentation peut être trouvée ici http://underscorejs.org/ ainsi que sa source sur github et diverses autres informations

Et enfin une implémentation lodash https://lodash.com/docs#size

_.size(obj)

Belldandu
la source
En réponse à vos commentaires sur Array(obj).length: Cela ne fonctionne pas. http://jsfiddle.net/Jhy8M/
Jamie Chong
ouais je l'ai regardé un peu plus je vais finir par supprimer cette réponse si possible ou tout simplement tout éditer ensemble
Belldandu
Je ne vois pas que cela a quelque chose à voir avec les fonctions, d'une part, et dans Chrome, je ne vois pas du tout ce comportement. Je soupçonne que cela peut avoir à voir avec le comportement par défaut d' Object.defineProperty (): qui est énumérablefalse , bien que je n'ai pas encore trouvé de documentation sur la façon dont var obj = { a: true, b: true }peut différer var obj = {}; obj.a = true; obj.b = true;ou simplement si une interprétation / sémantique différente du W3 a été adopté par Chrome.
Nolo
10

comme répondu ci-dessus: Object.keys(obj).length

Mais: comme nous avons maintenant une vraie classe Map dans ES6, je suggère de l'utiliser au lieu d'utiliser les propriétés d'un objet.

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

Pour ceux qui ont Underscore.js inclus dans leur projet, vous pouvez faire:

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

ou style fonctionnel:

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

Voici quelques tests de performances pour trois méthodes;

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

Object.keys (). Length

20 735 opérations par seconde

Très simple et compatible. Fonctionne rapidement mais cher car il crée un nouveau tableau de clés, qui est ensuite jeté.

return Object.keys(objectToRead).length;

parcourir les clés

15 734 opérations par seconde

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

Légèrement plus lent, mais loin de l'utilisation de la mémoire, donc probablement mieux si vous êtes intéressé par l'optimisation pour les machines mobiles ou autres petites

Utiliser la carte au lieu de l'objet

953 839 338 opérations par seconde

return mapToRead.size;

Fondamentalement, Map suit sa propre taille, nous ne faisons que renvoyer un champ numérique. Loin, bien plus vite que toute autre méthode. Si vous avez le contrôle de l'objet, convertissez-les à la place en cartes.

Steve Cooper
la source
6

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

Object.defineProperty (obj, prop, descriptor)

Vous pouvez soit l'ajouter à tous vos objets:

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

Ou un seul objet:

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

Exemple:

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

Ajouté de cette façon, il ne sera pas affiché dans les boucles for..in:

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

Production:

name:John Doe
email:leaked@example.com

Remarque: cela ne fonctionne pas dans les navigateurs <IE9.

lepe
la source
Si vous allez étendre des prototypes intégrés ou remplir une propriété (c'est-à-dire monkey-patch), veuillez le faire correctement: pour la compatibilité ascendante, vérifiez si la propriété existe d'abord, puis rendez la propriété non énumérable afin que les propres clés des objets construits ne sont pas pollués. Pour les méthodes, utilisez des méthodes réelles . Ma recommandation: suivez ces exemples qui montrent comment ajouter une méthode qui se comporte aussi étroitement que possible comme les méthodes intégrées.
user4642212
5

La façon dont j'ai résolu ce problème consiste à créer ma propre implémentation d'une liste de base qui conserve un enregistrement du nombre d'éléments stockés dans l'objet. C'est très simple. Quelque chose comme ça:

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];
   }
}
Cliquez Upvote
la source
1
Alternative intéressante. Cela élimine les frais généraux d'une fonction de comptage agrégé, mais au prix d'un appel de fonction à chaque fois que vous ajoutez ou supprimez un élément, ce qui peut malheureusement être pire. J'utiliserais personnellement une telle implémentation de liste pour l'encapsulation des données et les méthodes personnalisées qu'elle peut fournir par rapport à un tableau simple, mais pas quand j'ai juste besoin d'un comptage rapide des éléments.
Luc125
1
J'aime votre réponse, mais je suis aussi un lemming et un cliquetis. Cela présente un dilemme intéressant. Vous ne tenez pas compte d'un certain comportement dans vos instructions, comme ma situation où j'ai déjà voté positivement votre réponse, mais ensuite je suis invité à "cliquer sur vote positif" et je ne peux pas. L'instruction échoue silencieusement mais je comprends de votre contenu ici sur SO que l'échec silencieux n'est pas quelque chose que vous aimez faire votre code. Juste un avertissement.
L0j1k
1
J'aime vraiment cette réponse, belle structure de données. Et s'il y avait un impact sur les performances avec les appels de fonction sur add, il y aurait une amélioration des performances beaucoup plus importante si vous deviez parcourir un objet. Cela devrait permettre la boucle la plus rapidevar i = basiclist.count while(i--){...}
Lex
Une liste de base ne devrait-elle pas au moins comprendre des contrôles de base? Comme vérifier si addremplace un ancien élément ou si removeest appelé avec un index inexistant. De plus, il n'est pas possible de vérifier si la liste a un index donné s'il undefineds'agit d'une valeur d'élément valide.
Robert
2
Une liste doit être ordonnée et itérable. Les données sont stockées dans un objet, il n'y a donc aucune garantie sur l'ordre des éléments. Comment trouvez-vous la longueur d'une liste avec des trous dedans? this.count? La valeur d'index la plus élevée? Si vous ajoutez jamais deux éléments au même index, le compte passe dans un état d'erreur.
Ultimate Gobblement
4

Vous pouvez utiliser Object.keys(data).lengthpour trouver la longueur d'un objet JSON contenant des données clés

Codemaker
la source
3

Pour ceux qui ont Ext JS 4 dans leur projet, vous pouvez faire:

Ext.Object.getSize(myobj);

L'avantage de cela est qu'il fonctionnera sur tous les navigateurs compatibles Ext (IE6-IE8 inclus), cependant, je pense que le temps d'exécution n'est pas meilleur que O (n), comme avec les autres solutions suggérées.

Mark Rhodes
la source
2

Vous pouvez utiliser:

Object.keys(objectName).length; 

et

Object.values(objectName).length;
Fayaz
la source
1

OP n'a pas spécifié si l'objet est un nodeList, si c'est le cas, vous pouvez simplement utiliser la méthode de longueur directement dessus. Exemple:

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

Si jQuery ci-dessus ne fonctionne pas, essayez

$(Object.Item).length
codejoecode
la source
3
Il semble que ça Object.Itemn'existe pas
Luc125
-1

Je ne pense pas que ce soit possible (du moins pas sans utiliser des internes). Et je ne pense pas que vous gagneriez beaucoup en optimisant cela.

un mélange
la source
2
La réponse acceptée montre que cela peut être fait, et vous n'avez aucun contexte pour affirmer qu'il n'y a rien à gagner.
Conduit
-1

J'essaie de le rendre disponible à tous les objets comme celui-ci:

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