如何有效地计算JavaScript中对象的键/属性数量?


1543

计算对象的键/属性数的最快方法是什么?是否可以在不迭代对象的情况下执行此操作?即不做

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

(Firefox确实提供了一个魔术__count__属性,但是在版本4的某个位置将其删除。)



2
不同方式的性能基准:jsben.ch
#

Answers:


2513

要在任何与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/…

161
-1(如果可以的话,则为-200),这不仅会遍历对象,还会使用其所有键创建一个全新的数组,因此它完全无法回答问题。
GetFree 2012年

42
似乎比for的速度要快得多(至少在Chrome 25上如此):jsperf.com/count-elements-in-object
fserb 2012年

34
@GetFree为什么这么多竖起大拇指?就编码而言,这绝对是最快的方法。不需要额外的方法或库。就代码速度而言,显然也不错。根本没有完全失败。87对您来说失败了。
安德鲁(Andrew)

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 2012年

14
@styfle如果使用for循环迭代对象的属性,则还将在原型链中获取属性。这就是为什么hasOwnProperty需要检查的原因。它仅返回在对象本身上设置的属性。
Renaat De Muynck

13
@styfle为简化起见,您可以编写obj.hasOwnProperty(k)(我实际上在原始帖子中做了,但后来进行了更新)。hasOwnProperty可以在每个对象上使用,因为它是Object原型的一部分,但是在极少数情况下,该方法将被删除或覆盖,您可能会得到意想不到的结果。通过调用它Object.prototype可以使它更健壮。使用的原因call是因为您要在obj而不是原型上调用方法。
Renaat De Muynck


1
@XavierDelamotte您绝对正确。虽然我的版本适用,但它是非常基本的示例。Mozilla的代码更安全。(PS:您的链接也在接受的答案中)
Renaat De Muynck

137

如果你正在使用Underscore.js可以使用_.size(感谢@douwe):
_.size(obj)

另外,您也可以使用_.keys,对于某些用户可能更清楚:
_.keys(obj).length

我强烈推荐Underscore,它是一个紧凑的库,可以处理很多基本的事情。只要有可能,它们就会与ECMA5相匹配并遵循本机实现。

否则,我支持@Avi的答案。我对其进行了编辑,以添加指向MDC文档的链接,其中包括可以添加到非ECMA5浏览器的keys()方法。


9
如果您使用underscore.js,则应改用_.size。好处是,如果您以某种方式从数组切换到对象,反之亦然,则结果保持不变。
douwe 2011年

2
从我的理解来看,lodash通常比下划线更好(尽管他们做类似的事情)。
Merlyn Morgan-Graham

1
@ MerlynMorgan-Graham,如果我没记错的话,lodash最初是下划线的叉子……
molson504x 2015年

6
_.keys(obj).length最适合我,因为我的返回对象有时是一个纯字符串,其中没有属性。_.size(obj)给我回了字符串的长度,而_.keys(obj).length返回0
雅各布斯塔姆

O(n)复杂度。Lodash和Underscore在Object.keys内部使用。for..in如果Object.keys未定义,下划线还会将每个键复制到循环内的数组中。
N. Kudryavtsev

82

标准的Object实现(ES5.1对象内部属性和方法)不需要Object跟踪其键/属性的数量,因此,在Object不显式或隐式迭代其键的情况下,应该没有标准的方法来确定a的大小。

因此,这是最常用的替代方法:

1. ECMAScript的Object.keys()

Object.keys(obj).length;通过内部遍历键以计算临时数组并返回其长度来工作。

  • 优点 -可读且简洁的语法。如果没有本机支持,则不需要垫片或垫片,只需使用垫片即可
  • 缺点 -由于创建阵列而导致的内存开销。

2.基于图书馆的解决方案

在本主题的其他地方,许多基于库的示例在它们的库上下文中都是有用的成语。但是,从性能的角度来看,与完美的无库代码相比,没有任何收获,因为所有这些库方法实际上都封装了for循环或ES5 Object.keys(本机或填充)。

3.优化循环

最慢的部分这样的的for循环一般是.hasOwnProperty()呼叫,因为该函数调用开销。因此,当我只想要JSON对象的条目数时,.hasOwnProperty()如果我知道没有代码也不会扩展,就跳过该调用 Object.prototype

否则,可以通过使用klocal(var k)和使用前缀增量运算符(++count)而不是postfix 对代码进行非常小的优化。

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;

在给定的环境下这是否更快,这是基准测试的问题。无论如何,可以预期性能提升非常有限。


为什么要var k in myobj提高性能?据我所知,只有函数在JavaScript中声明新的作用域。循环中是否是此规则的例外?
Lars Gyrup Brink Nielsen 2014年

1
这样更快吗?for (var k in myobj) hasOwn.call(myobj, k) && ++count;即用简单的&&替换if语句?
Hamza Kubba 2014年

最后一件事你可以做:Object.getOwnPropertyNames(obj).length; 简单得多。
威尔特

29

如果您实际上遇到了性能问题,建议您使用一个函数来包装向对象添加属性或从对象中删除属性的调用,该函数还要增加/减少适当命名的(size?)属性。

您只需计算一次属性的初始数量,然后从那里继续。如果没有实际的性能问题,请不要打扰。只需将这段代码包装在一个函数中,getNumberOfProperties(object)然后完成它即可。


4
@hitautodestruct因为他提供了解决方案。
暗恋

@crush这个答案似乎是建议要做的事情,而不是给出直接的解决方案。
hitautodestruct

5
@hitautodestruct提供了一个答案:使用添加/删除方法增加/减少封装的计数。下面还有另一个完全像这样的答案。唯一的区别是,Confusion没有提供任何代码。答案并非仅提供代码解决方案。
2013年

1
它可能并不完美……但是与其他“答案”相比,这可能是某些情况下的最佳解决方案
d.raev '16

1
到目前为止,这是我看到的唯一解决方案,它是O(1)恒定时间性能复杂性,因此是回答“不进行迭代”问题详细信息的唯一解决方案,因此应该是tru接受的答案。大多数(如果不是全部)其他答案都不能回答这个问题,因为它们提供了O(n)线性时间性能复杂度;调用诸如.keys()函数之类的1行解决方案的情况也是如此,因为此类函数调用为O(n)。
cellepo

17

正如Avi Flax所说的那样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

但是,在大多数情况下,您可能不希望在这些类型的操作中包括无数变量,但是了解它们之间的差异总是很好的;)


1
竖起大拇指Object.getOwnPropertyNames,你是这里唯一的一个……
Wilt

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__在壁虎1.9.3被删除:whereswalden.com/2010/04/06/... 计数 -属性-对象- -是-被移除的/
dshaw

17
现在Firefox 4已经发布,这个答案已经过时了。Object.__count__消失了,也很好摆脱了。
Yi Jiang

1
我不会说答案已经过时了。将值封装在函数中仍然是一种有趣的策略。
devios1 2011年

应该使用原型对象进行扩展
Aaria Carter-Weir 2011年

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)


回应您对的评论Array(obj).length:它不起作用。http://jsfiddle.net/Jhy8M/
Jamie Chong

是的,我调查了更多即时消息,如果可能的话,最终我将删除此答案,或者只是一起编辑它
Belldandu 2014年

我没有看到这与功能有任何关系,一方面,在Chrome中我完全看不到这种行为。我怀疑这可能不得不做的默认行为Object.defineProperty():枚举false,虽然我还没有找到如何任何文件var obj = { a: true, b: true }可以从不同var obj = {}; obj.a = true; obj.b = true;或干脆如果W3的不同解释/语义学被Chrome浏览器采用。
2016年

10

如以上回答: Object.keys(obj).length

但是:正如我们现在在ES6中拥有一个真正的Map类一样,我建议使用它而不是使用对象的属性。

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

每秒20,735次操作

非常简单且兼容。运行速度快价格昂贵,因为它会创建一个新的键数组,然后将其丢弃。

return Object.keys(objectToRead).length;

遍历键

每秒15734次操作

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

速度稍慢,但与内存使用量相差无几,如果您有兴趣针对移动或其他小型计算机进行优化,则可能会更好

使用地图代替对象

每秒953,839,338次操作

return mapToRead.size;

基本上,地图会跟踪自己的大小,因此我们只返回一个数字字段。比任何其他方法快得多。如果您可以控制该对象,则可以将其转换为地图。


6

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

Object.defineProperty(obj,prop,描述符)

您可以将其添加到所有对象中:

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

1
有趣的选择。这确实消除了聚合计数函数的开销,但是每次您添加或删除元素时都要以函数调用为代价,这可能会更糟。与纯数组相比,我会个人使用这样的列表实现来实现其数据封装和自定义方法,但是当我只需要快速进行项目计数时,就不用了。
Luc125

1
我喜欢您的回答,但我也很留意,并单击了upvote。这提出了一个有趣的难题。您并没有考虑说明中的某些行为,例如我已经对您的答案进行了投票的情况,但随后我被指示“单击投票”,但不能。指令以静默方式失败,但是我从此处的内容中收集到,所以静默失败不是您喜欢代码执行的事情。只是抬起头。
L0j1k 2013年

1
我真的很喜欢这个答案,很好的数据结构。而且,如果对add进行函数调用会对性能产生影响,那么如果必须遍历一个对象,则可以大大提高性能。这应该允许最快的循环模式var i = basiclist.count while(i--){...}
Lex

基本清单至少不应该包括基本检查吗?就像检查是否add替换旧项目或是否remove用不存在的索引调用。另外,如果列表undefined有效,则无法检查列表是否具有给定的索引。
罗伯特·

2
列表应有序且可迭代。数据存储在一个对象中,因此不能保证元素的顺序。您如何找到带有孔的列表的长度?this.count?最高索引值?如果曾经在相同的索引处添加两个项目,则计数将进入错误状态。
2014年

4

您可以Object.keys(data).length用来查找具有关键数据的JSON对象的长度


3

对于在项目中具有Ext JS 4的用户,您可以执行以下操作:

Ext.Object.getSize(myobj);

这样做的好处是它可以在所有与Ext兼容的浏览器(包括IE6-IE8)上运行,但是,我相信运行时间并不比O(n)更好,就像其他建议的解决方案一样。


2

您可以使用:

Object.keys(objectName).length; 

Object.values(objectName).length;

1

OP没有指定对象是否为nodeList,如果是,则可以直接在其上使用length方法。例:

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


-1

我认为这是不可能的(至少在不使用某些内部组件的情况下)。而且我认为通过优化它不会带来太大收益。


2
接受的答案表明可以做到这一点,并且您没有上下文可以断言没有任何收获。
导管

-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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.