Как эффективно посчитать количество ключей / свойств объекта в JavaScript?

1545

Какой самый быстрый способ подсчитать количество ключей / свойств объекта? Можно ли сделать это без перебора объекта? т.е. без выполнения

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

(Firefox действительно предоставлял магическое __count__свойство, но это было удалено где-то в версии 4.)

MJS
источник
3
Связанный: stackoverflow.com/questions/5223/…
ripper234
2
тест производительности для разных способов: jsben.ch/#/oSt2p
EscapeNetscape
1
Возможный дубликат длины объекта JavaScript
Адам Кац

Ответы:

2519

Чтобы сделать это в любой ES5-совместимой среде , такой как Node , Chrome, IE 9+, Firefox 4+ или Safari 5+:

Object.keys(obj).length
Ави Лен
источник
8
Не только Node.js, но и любая среда, которая поддерживает ES5
Yi Jiang
59
Кстати ... только что провел несколько тестов ... этот метод выполняется за O (n) времени. Цикл for не намного хуже, чем этот метод. ** грустное лицо ** stackoverflow.com/questions/7956554/…
BMiner
161
-1 (-200, если бы я мог). Это не только перебирает объект, но и создает новый массив со всеми его ключами, поэтому он не может ответить на вопрос.
GetFree
42
Это кажется намного быстрее, чем выполнение for (по крайней мере, в Chrome 25): jsperf.com/count-elements-in-object
fserb
34
@ GetFree Почему так много больших пальцев? Это определенно самый быстрый способ с точки зрения кодирования. Никаких дополнительных методов или библиотек не требуется. С точки зрения скорости кода, видимо, это тоже не так уж плохо. Не полный провал вообще. 87 недурно для тебя.
Андрей
150

Вы можете использовать этот код:

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

Затем вы можете использовать это в старых браузерах:

var len = Object.keys(obj).length;
Ренаат де мюнк
источник
2
Какова цель проверки (Object.prototype.hasOwnProperty.call(obj, k))?
Styfle
14
@styfle Если вы используете цикл for для перебора свойств объекта, вы также получаете свойства в цепочке прототипов. Вот почему проверка hasOwnPropertyнеобходима. Он возвращает только свойства, установленные для самого объекта.
Ренаат Де Мейнк
13
@styfle Чтобы упростить его, вы можете просто написать obj.hasOwnProperty(k)(я действительно сделал это в своем первоначальном сообщении, но обновил его позже). hasOwnPropertyдоступен для каждого объекта, потому что он является частью Objectпрототипа, но в редких случаях, когда этот метод будет удален или переопределен, вы можете получить неожиданные результаты. Вызов его из Object.prototypeнего делает его немного более надежным. Причина использования callзаключается в том, что вы хотите вызывать метод objвместо прототипа.
Ренаат Де Мейнк
6
Не лучше ли использовать эту версию? developer.mozilla.org/en-US/docs/JavaScript/Reference/…
Ксавье Деламотт
1
@XavierDelamotte Вы абсолютно правы. Хотя моя версия работает, она очень проста и является примером. Код Mozilla более безопасен. (PS: Ваша ссылка также в принятом ответе)
Ренаат Де Мейнк
137

Если вы используете Underscore.js, вы можете использовать _.size (спасибо @douwe):
_.size(obj)

В качестве альтернативы вы также можете использовать _.keys, которые могут быть более понятными для некоторых:
_.keys(obj).length

Я настоятельно рекомендую Underscore, его компактную библиотеку для выполнения множества базовых задач. По мере возможности они соответствуют ECMA5 и относятся к собственной реализации.

В противном случае я поддерживаю ответ @ Avi. Я отредактировал его, чтобы добавить ссылку на документ MDC, который включает метод keys (), который можно добавить в браузеры не ECMA5.

studgeek
источник
9
Если вы используете underscore.js, вам следует использовать _.size. Хорошо, что если вы как-то переключаетесь с массива на объект или наоборот, результат остается прежним.
До
2
И из моего понимания lodash обычно лучше, чем подчеркивание (хотя они делают подобные вещи).
Мерлин Морган-Грэм
1
@ MerlynMorgan-Graham, если я правильно помню, lodash изначально был форком подчеркивания ...
molson504x
6
_.keys(obj).lengthработал лучше всего для меня, потому что мой возвращаемый объект иногда представляет собой простую строку без каких-либо свойств внутри него. _.size(obj)возвращает мне длину строки, а _.keys(obj).lengthвозвращает 0.
Джейкоб Стамм
O (n) сложность . Lodash и Underscore используют Object.keysвнутри. Подчеркивание также копирует каждый ключ в массив внутри for..inцикла, если Object.keysон не определен.
Н. Кудрявцев
82

Стандартная реализация объекта ( ES5.1 «Внутренние свойства и методы объекта» ) не требует Objectотслеживания количества ключей / свойств, поэтому не должно быть стандартного способа определения размера Objectбез явной или неявной итерации его ключей.

Итак, вот наиболее часто используемые альтернативы:

1. ECMAScript Object.keys ()

Object.keys(obj).length;Работает путем внутренней перебора ключей для вычисления временного массива и возвращает его длину.

  • Плюсы - Читаемый и чистый синтаксис. Не требуется никакой библиотеки или пользовательского кода, кроме прокладки, если встроенная поддержка недоступна
  • Минусы - накладные расходы памяти из-за создания массива.

2. Библиотечные решения

Многие основанные на библиотеках примеры в других разделах этой темы являются полезными идиомами в контексте их библиотеки. Однако с точки зрения производительности выиграть нечего по сравнению с идеальным кодом без библиотеки, поскольку все эти библиотечные методы на самом деле инкапсулируют либо цикл for, либо ES5 Object.keys(нативный или отстраненный).

3. Оптимизация цикла for

Самая медленная часть такого цикла for - это обычно .hasOwnProperty()вызов из-за накладных расходов на вызов функции. Поэтому, когда мне просто нужно количество записей в объекте JSON, я просто пропускаю .hasOwnProperty()вызов, если знаю, что ни один код не расширялся и не будет расширяться Object.prototype.

В противном случае ваш код можно было бы немного оптимизировать, сделав klocal ( var k) и используя префикс-инкремент оператора ( ++count) вместо постфикса.

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

Другая идея основана на кэшировании hasOwnPropertyметода:

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

Является ли это быстрее или нет в данной среде - это вопрос сравнительного анализа. В любом случае можно ожидать очень ограниченного прироста производительности.

Luc125
источник
Почему бы var k in myobjповысить производительность? Насколько я знаю, только функции объявляют новую область видимости в JavaScript. Являются ли in-loop исключением из этого правила?
Ларс Гируп Бринк Нильсен
1
Это быстрее? for (var k in myobj) hasOwn.call(myobj, k) && ++count;т.е. заменяя оператор if простым &&?
Хамза Кубба
Последнее, что вы можете сделать с Object.getOwnPropertyNames(obj).length:; намного проще
завянет
29

Если вы действительно столкнулись с проблемой производительности, я бы предложил обернуть вызовы, которые добавляют / удаляют свойства к / из объекта, с функцией, которая также увеличивает / уменьшает свойство с соответствующим именем (size?).

Вам нужно только рассчитать начальное количество свойств один раз и двигаться дальше. Если нет реальной проблемы с производительностью, не беспокойтесь. Просто оберните этот фрагмент кода в функцию getNumberOfProperties(object)и покончите с этим.

Спутанность сознания
источник
4
@hitautodestruct, потому что он предлагает решение.
раздавить
@crush Этот ответ, кажется, предлагает вещи, а не дает прямое решение.
Hitautodestruct
5
@hitautodestruct предлагает ответ: увеличение / уменьшение инкапсулированного счетчика с помощью методов добавления / удаления. Ниже приведен другой ответ, точно такой же, как этот. Разница лишь в том, что Confusion не предлагал никакого кода. Ответы не обязаны предоставлять только решения кода.
раздавить
1
это может быть не идеально ... но по сравнению с другими "ответами" это может быть лучшим решением для некоторых ситуаций
d.raev
1
Пока что это единственное решение, которое я вижу, это O (1) сложность производительности с постоянным временем, и, следовательно, это единственное решение, которое отвечает на детали вопроса «без итерации» и поэтому должно быть истинным принятым ответом. Большинство, если не все другие ответы не отвечают на это, потому что они предлагают O (n) линейную сложность производительности времени; это также относится к однострочным решениям, которые вызывают что-то вроде функции .keys (), так как такие вызовы функций O (n).
виолончель
17

Как заявил Ави Лен https://stackoverflow.com/a/4889658/1047014

Object.keys(obj).length

будет делать трюк для всех перечисляемых свойств вашего объекта, но также включать неперечислимые свойства, которые вы можете вместо этого использовать Object.getOwnPropertyNames. Вот разница:

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

Как указано здесь, он имеет ту же поддержку браузера, что иObject.keys

Тем не менее, в большинстве случаев, вы можете не захотеть включать не перечисляемые в этот тип операций, но всегда полезно знать разницу;)

BenderTheOffender
источник
1
Спасибо за упоминание Object.getOwnPropertyNames, вы были здесь единственным ...
Уилт
15

Я не знаю ни одного способа сделать это, однако, чтобы свести итерации к минимуму, вы можете попробовать проверить наличие __count__и, если он не существует (то есть не Firefox), то вы можете перебрать объект и определить это для последующего использования, например:

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

Таким образом, любой поддерживающий браузер __count__будет использовать это, и итерации будут выполняться только для тех, которые этого не делают. Если счетчик меняется и вы не можете этого сделать, вы всегда можете сделать его функцией:

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

Таким образом, в любое время вы ссылаетесь на myobj. __count__функция сработает и пересчитает.

Люк Беннетт
источник
13
Обратите внимание, Object.prototype.__count__что в Gecko 1.9.3 удаляется : whereswalden.com/2010/04/06/… count -property -of-objects-is-
be
17
Теперь, когда вышел Firefox 4, этот ответ устарел. Object.__count__ушел, и хорошее избавление тоже.
И Цзян
1
Я бы не сказал, что ответ устарел. Это все еще интересная стратегия для инкапсуляции значения в функции.
devios1
следует использовать объект-прототип для расширения
Aaria Carter-Weir
13

Итерации по Avi Flax ответ Object.keys (obj) .length правильно для объекта, к которому не привязаны функции

пример:

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

против

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

шаги, чтобы избежать этого:

  1. не помещайте функции в объект, который вы хотите посчитать количество ключей в

  2. используйте отдельный объект или создайте новый объект специально для функций (если вы хотите подсчитать, сколько функций используется в файле Object.keys(obj).length)

также да, я использовал _ или модуль подчеркивания из nodejs в моем примере

документацию можно найти здесь http://underscorejs.org/, а также ее источник на github и другую другую информацию

И, наконец, реализация lodash https://lodash.com/docs#size

_.size(obj)

Belldandu
источник
В ответ на ваши комментарии о Array(obj).length: Это не работает. http://jsfiddle.net/Jhy8M/
Джейми Чонг
да, я посмотрел на это немного больше, я собираюсь в конечном итоге удалить этот ответ, если это возможно, или просто отредактировать его все вместе
Belldandu
Я не вижу, что это имеет какое-либо отношение к функциям, например, и в Chrome я не вижу такого поведения вообще. Я подозреваю, что это, возможно, было связано с поведением по умолчанию Object.defineProperty (): enumerable, которое есть false, хотя я еще не нашел никакой документации о том, как var obj = { a: true, b: true }может отличаться var obj = {}; obj.a = true; obj.b = true;или просто если другая интерпретация / семантика W3 имеет был принят Chrome.
Ноло
10

как ответили выше: Object.keys(obj).length

Но: поскольку у нас теперь есть реальный класс Map в ES6, я бы предложил использовать его вместо использования свойств объекта.

const map = new Map();
map.set("key", "value");
map.size; // THE fastest way
Флавиен Волкен
источник
8

Для тех, у кого есть Underscore.js, включенный в их проект, вы можете сделать:

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

или функциональный стиль:

_.size({a:'', b:''}) // => 2
Хакунин
источник
8

Вот несколько тестов производительности для трех методов;

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

Object.keys (). Длина

20 735 операций в секунду

Очень просто и совместимо. Работает быстро, но дорого, потому что создает новый массив ключей, который затем отбрасывается.

return Object.keys(objectToRead).length;

перебрать ключи

15 734 операций в секунду

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

Немного медленнее, но не близко к использованию памяти, поэтому, вероятно, лучше, если вы заинтересованы в оптимизации для мобильных или других небольших машин

Использование карты вместо объекта

953 839 338 операций в секунду

return mapToRead.size;

По сути, Map отслеживает свой собственный размер, поэтому мы просто возвращаем числовое поле. Гораздо быстрее, чем любой другой метод. Если у вас есть контроль над объектом, вместо этого конвертируйте их в карты.

Стив Купер
источник
6

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

Object.defineProperty (obj, prop, descriptor)

Вы можете добавить его ко всем вашим объектам:

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

Или один объект:

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

Пример:

var myObj = {};
myObj.name  = "John Doe";
myObj.email = "leaked@example.com";
myObj.length; //output: 2

Добавленный таким образом, он не будет отображаться в циклах for..in:

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

Вывод:

name:John Doe
email:leaked@example.com

Примечание: он не работает в браузерах <IE9.

Лепе
источник
Если вы собираетесь расширить встроенные прототипы или заполнить свойство (например, monkey-patch), пожалуйста, сделайте это правильно: для прямой совместимости сначала проверьте, существует ли свойство, а затем сделайте свойство не перечисляемым, чтобы собственные ключи из построенных объектов не загрязнены. Для методов используйте актуальные методы . Моя рекомендация: следуйте этим примерам, которые демонстрируют, как добавить метод, который ведет себя как можно более близко, как встроенные методы.
user4642212
5

Я решил эту проблему, создав собственную реализацию базового списка, который хранит записи о том, сколько элементов хранится в объекте. Это очень просто. Что-то вроде этого:

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];
   }
}
Нажмите Upvote
источник
1
Интересная альтернатива. Это исключает накладные расходы функции статистического подсчета, но за счет вызова функции каждый раз, когда вы добавляете или удаляете элемент, что, к сожалению, может быть хуже. Я бы лично использовал такую ​​реализацию списка для инкапсуляции данных и пользовательских методов, которые он может предоставить по сравнению с обычным массивом, но не тогда, когда мне просто нужен быстрый подсчет элементов.
Luc125
1
Мне нравится ваш ответ, но я также лемминг и щелкнул upvote. Это представляет интересную дилемму. Вы не учитываете какое-либо поведение в своих инструкциях, например, мою ситуацию, когда я уже проголосовал против вашего ответа, но затем я получил указание «нажать upvote» и не могу. Инструкция молчаливо проваливается, но я понял из вашего контента на SO, что молчаливый сбой - это не то, что вам нравится в вашем коде. Просто один на один.
L0j1k
1
Мне очень нравится этот ответ, хорошая структура данных. И если бы при вызове функции было добавлено влияние на производительность, было бы гораздо большее повышение производительности, если бы пришлось перебирать объект. Это должно учесть самый быстрый петлевой паттенvar i = basiclist.count while(i--){...}
Lex
Не должен ли базовый список хотя бы включать базовые проверки? Как проверка, addзаменяет ли старый элемент или removeвызывается с несуществующим индексом. Также невозможно проверить, имеет ли список заданный индекс, если undefinedявляется допустимым значением элемента.
Роберт
2
Список должен быть упорядоченным и повторяемым. Данные хранятся в объекте, поэтому нет гарантии на порядок элементов. Как вы находите длину списка с отверстиями в нем? this.count? Самое высокое значение индекса? Если вы когда-нибудь добавите два элемента с одинаковым индексом, счетчик перейдет в состояние ошибки.
Ultimate Gobblement
4

Вы можете использовать, Object.keys(data).lengthчтобы найти длину объекта JSON, имеющего ключевые данные

Codemaker
источник
3

Для тех, кто имеет Ext JS 4 в своем проекте, вы можете сделать:

Ext.Object.getSize(myobj);

Преимущество этого состоит в том, что он будет работать во всех совместимых с Ext браузерах (включая IE6-IE8), однако, я считаю, что время выполнения не лучше, чем O (n), как и в других предлагаемых решениях.

Марк Родос
источник
2

Ты можешь использовать:

Object.keys(objectName).length; 

а также

Object.values(objectName).length;
Fayaz
источник
1

OP не указал, является ли объект нодлистом, если это так, то вы можете просто использовать метод длины непосредственно для него. Пример:

buttons = document.querySelectorAll('[id=button)) {
console.log('Found ' + buttons.length + ' on the screen'); 
Роберт Синклер
источник
0

Если jQuery выше не работает, то попробуйте

$(Object.Item).length
codejoecode
источник
3
Кажется, что Object.Itemне существует
Luc125
-1

Я не думаю, что это возможно (по крайней мере, без использования некоторых внутренних). И я не думаю, что вы бы много выиграли, оптимизировав это.

смесь
источник
2
Принятый ответ показывает, что это можно сделать, и у вас нет контекста, чтобы утверждать, что нечего получить.
Conduit
-1

Я пытаюсь сделать его доступным для всех объектов следующим образом:

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
источник