Jak efektywnie policzyć liczbę kluczy / właściwości obiektu w JavaScript?

1544

Jaki jest najszybszy sposób na policzenie liczby kluczy / właściwości obiektu? Czy można to zrobić bez iteracji nad obiektem? tzn. bez robienia

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

(Firefox zapewnił magiczną __count__właściwość, ale została ona usunięta gdzieś w pobliżu wersji 4.)

mjs
źródło
3
Powiązane: stackoverflow.com/questions/5223/…
ripper234 31.01.12
2
test wydajności na różne sposoby: jsben.ch/#/oSt2p
EscapeNetscape
1
Możliwy duplikat długości obiektu JavaScript
Adam Katz

Odpowiedzi:

2515

Aby to zrobić w dowolnym środowisku zgodnym z ES5 , takim jak Node , Chrome, IE 9+, Firefox 4+ lub Safari 5+:

Object.keys(obj).length
Avi Len
źródło
8
Nie tylko Node.js, ale każde środowisko obsługujące ES5
Yi Jiang
59
BTW ... właśnie przeprowadziłem kilka testów ... ta metoda działa w czasie O (n). Pętla for nie jest dużo gorsza niż ta metoda. ** smutna twarz ** stackoverflow.com/questions/7956554/…
BMiner
161
-1 (-200, gdybym mógł) To nie tylko iteruje obiekt, ale także tworzy zupełnie nową tablicę ze wszystkimi jego kluczami, więc całkowicie nie odpowiada na pytanie.
GetFree
42
Wydaje się znacznie szybszy niż robienie (przynajmniej w Chrome 25): jsperf.com/count-elements-in-object
fserb
34
@ GetFree Dlaczego tak wiele kciuków w górę? Jest to zdecydowanie najszybszy sposób pod względem kodowania. Nie są wymagane żadne dodatkowe metody ani biblioteki. Pod względem szybkości kodu najwyraźniej też nie jest tak źle. Wcale nie jest to kompletna porażka. 87 kciuki do góry zawodzą.
Andrew
150

Możesz użyć tego kodu:

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

Następnie możesz użyć tego również w starszych przeglądarkach:

var len = Object.keys(obj).length;
Renaat De Muynck
źródło
2
Jaki jest cel kontroli (Object.prototype.hasOwnProperty.call(obj, k))?
styfle
14
@styfle Jeśli użyjesz pętli for do iteracji właściwości obiektu, otrzymasz również właściwości w łańcuchu prototypów. Dlatego hasOwnPropertykonieczne jest sprawdzenie . Zwraca tylko właściwości ustawione na samym obiekcie.
Renaat De Muynck
13
@styfle Aby uprościć, możesz po prostu napisać obj.hasOwnProperty(k)(faktycznie zrobiłem to w moim oryginalnym poście, ale zaktualizowałem go później). hasOwnPropertyjest dostępny na każdym obiekcie, ponieważ jest on częścią Objectprototypu, ale w rzadkich przypadkach, gdy ta metoda zostanie usunięta lub zastąpiona, możesz otrzymać nieoczekiwane wyniki. Wywołanie go z Object.prototypeniego czyni go trochę bardziej niezawodnym. Powodem użycia calljest to, że chcesz wywołać metodę objzamiast na prototypie.
Renaat De Muynck
6
Czy nie byłoby lepiej używać tej wersji? developer.mozilla.org/en-US/docs/JavaScript/Reference/…
Xavier Delamotte
1
@XavierDelamotte Masz absolutną rację. Chociaż moja wersja działa, jest bardzo prosta i stanowi przykład. Kod Mozilli jest bezpieczniejszy. (PS: Twój link znajduje się również w zaakceptowanej odpowiedzi)
Renaat De Muynck 30.01.2013
137

Jeśli używasz Underscore.js , możesz użyć _.size (dzięki @douwe):
_.size(obj)

Alternatywnie możesz również użyć klawiszy _., Które mogą być bardziej zrozumiałe dla niektórych:
_.keys(obj).length

Bardzo polecam Underscore, to ciasna biblioteka do robienia wielu podstawowych rzeczy. Tam, gdzie to możliwe, dopasowują ECMA5 i przechodzą na implementację natywną.

W przeciwnym razie popieram odpowiedź @ Avi. Zredagowałem go, aby dodać link do dokumentu MDC, który zawiera metodę keys (), którą możesz dodać do przeglądarek innych niż ECMA5.

studgeek
źródło
9
Jeśli używasz underscore.js, powinieneś użyć _.size. Dobrą rzeczą jest to, że jeśli w jakiś sposób przełączysz się z tablicy na obiekt lub odwrotnie, wynik pozostanie taki sam.
douwe
2
Z mojego zrozumienia, lodash jest ogólnie lepszy niż podkreślenie (chociaż robią podobne rzeczy).
Merlyn Morgan-Graham
1
@ MerlynMorgan-Graham, jeśli dobrze pamiętam, lodash jest pierwotnie widelcem podkreślenia ...
molson504x
6
_.keys(obj).lengthdziałało najlepiej dla mnie, ponieważ mój obiekt zwracany jest czasem zwykłym ciągiem bez właściwości. _.size(obj)zwraca mi długość łańcucha, a _.keys(obj).lengthzwraca 0.
Jacob Stamm,
O (n) złożoność . Lodash i podkreślenie używają Object.keyswewnętrznie. Znak podkreślenia kopiuje również każdy klucz do tablicy wewnątrz for..inpętli, jeśli Object.keysnie jest zdefiniowany.
N. Kudryavtsev
82

Standardowa implementacja obiektu ( ES5.1 Object Internal Properties and Methods ) nie wymaga Objectśledzenia liczby kluczy / właściwości, dlatego nie powinno być standardowego sposobu określania wielkości Objectbez jawnego lub niejawnego iterowania kluczy.

Oto najczęściej stosowane alternatywy:

1. Object.keys () w ECMAScript

Object.keys(obj).length;Działa poprzez wewnętrzne iterowanie po klawiszach w celu obliczenia tablicy tymczasowej i zwraca jej długość.

  • Plusy - czytelna i czysta składnia. Nie wymaga biblioteki ani niestandardowego kodu, z wyjątkiem podkładki, jeśli natywna obsługa jest niedostępna
  • Wady - narzut pamięci związany z tworzeniem tablicy.

2. Rozwiązania oparte na bibliotece

Wiele przykładów opartych na bibliotece gdzie indziej w tym temacie to przydatne idiomy w kontekście ich biblioteki. Jednak z punktu widzenia wydajności nie ma nic do zyskania w porównaniu z doskonałym kodem bez biblioteki, ponieważ wszystkie te metody biblioteczne faktycznie otaczają pętlę for lub ES5 Object.keys(natywną lub shimmed ).

3. Optymalizacja pętli for

Najwolniej część takiej dla pętli jest na ogół .hasOwnProperty()wywołania z powodu obciążania wywołaniu funkcji. Kiedy więc chcę tylko liczby wpisów obiektu JSON, po prostu pomijam .hasOwnProperty()wywołanie, jeśli wiem, że żaden kod nie był ani nie będzie rozszerzany Object.prototype.

W przeciwnym razie twój kod może być bardzo nieznacznie zoptymalizowany poprzez utworzenie klocal ( var k) i użycie operatora increment-increment ( ++count) zamiast postfix.

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

Kolejny pomysł polega na buforowaniu hasOwnPropertymetody:

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

To, czy jest to szybsze, czy nie w danym środowisku, jest kwestią testu porównawczego. W każdym razie można oczekiwać bardzo ograniczonego wzrostu wydajności.

Luc125
źródło
Po co var k in myobjzwiększać wydajność? O ile mi wiadomo, tylko funkcje deklarują nowy zakres w JavaScript. Czy pętle wewnętrzne są wyjątkiem od tej reguły?
Lars Gyrup Brink Nielsen
1
Czy to jest szybsze? for (var k in myobj) hasOwn.call(myobj, k) && ++count;tj. zastąpienie instrukcji if prostym znakiem &&?
Hamza Kubba,
Ostatnia rzecz, którą możesz zrobić z Object.getOwnPropertyNames(obj).length:; o wiele prostsze.
Wilt
29

Jeśli faktycznie masz problem z wydajnością, sugerowałbym zawinięcie wywołań dodających / usuwających właściwości do / z obiektu funkcją, która również zwiększa / zmniejsza odpowiednio nazwaną właściwość (rozmiar?).

Wystarczy tylko raz obliczyć początkową liczbę nieruchomości i przejść od tego miejsca. Jeśli nie ma rzeczywistego problemu z wydajnością, nie przejmuj się. Po prostu zawiń ten fragment kodu w funkcji getNumberOfProperties(object)i gotowe.

Dezorientacja
źródło
4
@hitautodestruct Ponieważ oferuje rozwiązanie.
zmiażdżyć
@crush Ta odpowiedź wydaje się sugerować, co należy zrobić, a nie dać bezpośrednie rozwiązanie.
hitautodestruct
5
@hitautodestruct sugeruje odpowiedź: zwiększanie / zmniejszanie liczby enkapsulowanej metodami dodawania / usuwania. Istnieje inna odpowiedź dokładnie taka jak poniżej. Jedyna różnica polega na tym, że Confusion nie oferuje żadnego kodu. Odpowiedzi nie są wymagane do dostarczania wyłącznie rozwiązań kodowych.
zmiażdżyć
1
to może nie być idealne ... ale w porównaniu z innymi „odpowiedziami” może to być najlepsze rozwiązanie w niektórych sytuacjach
d.raev
1
Jak dotąd jest to jedyne rozwiązanie, które widzę, to jest stała złożoność O (1), a zatem jest to jedyne rozwiązanie, które odpowiada na pytanie szczegółowe „bez iteracji” i dlatego powinno być prawdziwą odpowiedzią zaakceptowaną. Większość, jeśli nie wszystkie inne odpowiedzi nie odpowiadają na to pytanie, ponieważ oferują one złożoność wydajności liniowej w czasie O (n); dotyczy to również rozwiązań 1-liniowych, które wywołują funkcję podobną do .keys (), ponieważ takie wywołania funkcji to O (n).
cellepo
17

Jak stwierdził Avi Flax https://stackoverflow.com/a/4889658/1047014

Object.keys(obj).length

zrobi lewę dla wszystkich wyliczalnych właściwości w twoim obiekcie, ale także włączy właściwości niewymierne, których możesz zamiast tego użyć Object.getOwnPropertyNames. Oto różnica:

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

Jak stwierdzono tutaj, ma to takie samo wsparcie przeglądarki jakObject.keys

Jednak w większości przypadków możesz nie chcieć uwzględniać niezliczonych elementów w tego typu operacjach, ale zawsze dobrze jest znać różnicę;)

BenderTheOffender
źródło
1
Kciuki za wzmiankę Object.getOwnPropertyNames, byłeś tu jedynym ...
Wilt
15

Nie znam żadnego sposobu, aby to zrobić, jednak aby zminimalizować liczbę iteracji, możesz spróbować sprawdzić istnienie, __count__a jeśli nie istnieje (tzn. Nie Firefox), możesz iterować obiekt i zdefiniować do późniejszego wykorzystania np .:

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

W ten sposób skorzysta z niego dowolna obsługa przeglądarki __count__, a iteracje będą przeprowadzane tylko dla tych, które tego nie robią. Jeśli liczba się zmienia i nie możesz tego zrobić, zawsze możesz ustawić funkcję:

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

W ten sposób za każdym razem, gdy odwołujesz się do myobj. __count__funkcja uruchomi się i przeliczy.

Luke Bennett
źródło
13
Zauważ, że Object.prototype.__count__jest usuwany w Gecko 1.9.3: whereswalden.com/2010/04/06/… count -property-of-objects-is-being-
remove
17
Teraz, gdy Firefox 4 jest niedostępny, ta odpowiedź jest już nieaktualna. Object.__count__zniknął, a także dobra gra.
Yi Jiang
1
Nie powiedziałbym, że odpowiedź jest przestarzała. Wciąż interesująca jest strategia kapsułkowania wartości w funkcji.
devios1
powinien używać prototypowego obiektu do rozszerzenia
Aaria Carter-Weir
13

Aby wykonać iterację na Avi Flax, odpowiedz Object.keys (obj) .length jest poprawny dla obiektu, który nie ma powiązanych z nim funkcji

przykład:

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

przeciw

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

kroki, aby tego uniknąć:

  1. nie umieszczaj funkcji w obiekcie, w którym chcesz policzyć liczbę kluczy

  2. użyj osobnego obiektu lub stwórz nowy obiekt specjalnie dla funkcji (jeśli chcesz policzyć, ile funkcji jest w pliku za pomocą Object.keys(obj).length)

również tak, w moim przykładzie użyłem modułu _ lub podkreślenia z nodejs

dokumentację można znaleźć tutaj http://underscorejs.org/, a także jej źródło na github i różne inne informacje

I wreszcie implementacja lodash https://lodash.com/docs#size

_.size(obj)

Belldandu
źródło
W odpowiedzi na twoje komentarze na temat Array(obj).length: To nie działa. http://jsfiddle.net/Jhy8M/
Jamie Chong,
tak, przyjrzałem się temu trochę więcej, ostatecznie skończę usuwając tę ​​odpowiedź, jeśli to możliwe, lub po prostu edytując wszystko razem
Belldandu
Nie widzę, żeby miało to jakiś związek z funkcjami, a w Chrome w ogóle nie widzę tego zachowania. Podejrzewam, że mogło to mieć związek z domyślnym zachowaniem Object.defineProperty (): enumerable, który jest false, chociaż nie znalazłem jeszcze żadnej dokumentacji na temat tego, co var obj = { a: true, b: true }może się różnić var obj = {}; obj.a = true; obj.b = true;lub po prostu, jeśli inna interpretacja / semantyka W3 ma zostały adoptowane przez Chrome.
Nolo
10

jak odpowiedziano powyżej: Object.keys(obj).length

Ale: ponieważ mamy teraz prawdziwą klasę Map w ES6, sugerowałbym użycie jej zamiast używania właściwości obiektu.

const map = new Map();
map.set("key", "value");
map.size; // THE fastest way
Flavien Volken
źródło
8

Dla tych, którzy mają Underscore.js w swoim projekcie, możesz:

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

lub funkcjonalny styl:

_.size({a:'', b:''}) // => 2
hakunin
źródło
8

Oto kilka testów wydajności dla trzech metod;

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

Object.keys (). Długość

20 735 operacji na sekundę

Bardzo prosty i kompatybilny. Działa szybko, ale jest drogi, ponieważ tworzy nowy zestaw kluczy, który następnie zostaje wyrzucony.

return Object.keys(objectToRead).length;

zapętlić klucze

15 734 operacji na sekundę

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

Nieco wolniej, ale nigdzie w pobliżu użycia pamięci, więc prawdopodobnie lepiej, jeśli chcesz zoptymalizować pod kątem urządzeń mobilnych lub innych małych komputerów

Używanie mapy zamiast obiektu

953 839 338 operacji na sekundę

return mapToRead.size;

Zasadniczo mapa śledzi swój własny rozmiar, więc zwracamy tylko pole liczbowe. Daleko, znacznie szybciej niż jakakolwiek inna metoda. Jeśli masz kontrolę nad obiektem, zamień je na mapy.

Steve Cooper
źródło
6

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

Object.defineProperty (obj, prop, deskryptor)

Możesz dodać go do wszystkich swoich obiektów:

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

Lub pojedynczy obiekt:

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

Przykład:

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

Dodane w ten sposób nie będzie wyświetlane w pętlach for..in:

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

Wynik:

name:John Doe
email:leaked@example.com

Uwaga: nie działa w <przeglądarkach IE9.

lepe
źródło
Jeśli zamierzasz rozszerzyć wbudowane prototypy lub uzupełnić właściwość (np. Łatka małpy), zrób to poprawnie: w celu uzyskania zgodności z poprzednimi wersjami sprawdź, czy właściwość istnieje jako pierwsza, a następnie ustaw właściwość niepoliczalną, aby własne klucze zbudowanych obiektów nie są zanieczyszczone. Do metod używaj rzeczywistych metod . Moja rekomendacja: postępuj zgodnie z tymi przykładami, które pokazują, jak dodać metodę, która zachowuje się tak blisko, jak to możliwe, jak metody wbudowane.
user4642212
5

Rozwiązałem ten problem, tworząc własną implementację podstawowej listy, która prowadzi rejestr liczby elementów przechowywanych w obiekcie. To jest bardzo proste. Coś takiego:

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];
   }
}
Kliknij opcję Upvote
źródło
1
Ciekawa alternatywa. Eliminuje to narzut związany z funkcją zliczania agregatów, ale kosztem wywołania funkcji za każdym razem, gdy dodajesz lub usuwasz element, co niestety może być gorsze. Osobiście użyłbym takiej implementacji listy do enkapsulacji danych i niestandardowych metod, które może ona zapewnić w porównaniu do zwykłej tablicy, ale nie wtedy, gdy potrzebuję tylko szybkiego liczenia elementów.
Luc125
1
Podoba mi się twoja odpowiedź, ale jestem też lemingiem i kliknąłem upvote. To stanowi ciekawy dylemat. W swoich instrukcjach nie bierzesz pod uwagę niektórych zachowań, takich jak moja sytuacja, w której już głosowałem twoją odpowiedź, ale potem otrzymałem polecenie „kliknij głosowanie” i nie mogę. Instrukcja kończy się niepowodzeniem w trybie cichym, ale z twoich treści tutaj, na SO, zbieram informacje, że niepowodzenie w trybie cichym nie jest czymś, co lubisz robić w kodzie. Tylko jedna głowa do góry.
L0j1k
1
Naprawdę podoba mi się ta odpowiedź, ładna struktura danych. A jeśli wystąpiłby wpływ na wydajność z wywołaniami funkcji przy dodawaniu, byłoby znacznie większe zwiększenie wydajności, gdyby trzeba było iterować nad obiektem. To powinno pozwolić na najszybszą pętlęvar i = basiclist.count while(i--){...}
Lex
Czy podstawowa lista nie powinna obejmować przynajmniej podstawowych kontroli? Jak sprawdzenie, czy addzastępuje stary element lub czy removejest wywoływany z nieistniejącym indeksem. Nie można również sprawdzić, czy lista ma podany indeks, czy undefinedjest prawidłową wartością pozycji.
Robert
2
Lista powinna być uporządkowana i powtarzalna. Dane są przechowywane w obiekcie, więc nie ma gwarancji na kolejność elementów. Jak znaleźć długość listy z dziurami? this.count? Najwyższa wartość indeksu? Jeśli kiedykolwiek dodasz dwa elementy pod tym samym indeksem, licznik przechodzi w stan błędu.
Ultimate Gobblement,
4

Możesz użyć Object.keys(data).lengthdo znalezienia długości obiektu JSON zawierającego kluczowe dane

Codemaker
źródło
3

Dla tych, którzy mają Ext JS 4 w swoim projekcie, możesz:

Ext.Object.getSize(myobj);

Zaletą tego jest to, że będzie działać na wszystkich przeglądarkach kompatybilnych z Ext (włącznie z IE6-IE8), jednak uważam, że czas działania nie jest lepszy niż O (n), jak w przypadku innych sugerowanych rozwiązań.

Mark Rhodes
źródło
2

Możesz użyć:

Object.keys(objectName).length; 

i

Object.values(objectName).length;
Fajaz
źródło
1

OP nie określił, czy obiekt jest nodeList, jeśli tak, to możesz po prostu bezpośrednio użyć metody length . Przykład:

buttons = document.querySelectorAll('[id=button)) {
console.log('Found ' + buttons.length + ' on the screen'); 
Robert Sinclair
źródło
0

Jeśli powyższe jQuery nie działa, spróbuj

$(Object.Item).length
kod kodowy
źródło
3
Wygląda na to, że Object.Itemnie istnieje
Luc125
-1

Nie sądzę, że jest to możliwe (przynajmniej nie bez użycia elementów wewnętrznych). I nie sądzę, byś dużo zyskał, optymalizując to.

amix
źródło
2
Zaakceptowana odpowiedź pokazuje, że można to zrobić i nie masz kontekstu, aby twierdzić, że nie ma nic do zyskania.
Conduit
-1

Staram się udostępnić go wszystkim takim obiektom:

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