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

1987

如何快速计算对象的键/属性数量?是否可以在不迭代对象的情况下完成?即,不执行以下操作:

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

(Firefox曾经提供了一个神奇的__count__属性,但在4版本左右被移除了。)


3
相关链接:https://dev59.com/QXVD5IYBdhLWcg3wWaVh - ripper234
2
不同方式的性能基准测试:http://jsben.ch/#/oSt2p - EscapeNetscape
2
可能是JavaScript对象的长度的重复问题。 - Adam Katz
如果您需要计算映射,那可能意味着是时候考虑使用 Map 了。 - Константин Ван
20个回答

3135

9
不仅仅是Node.js,任何支持ES5的环境都可以。 - Yi Jiang
71
顺便说一下,我刚刚运行了一些测试,这种方法的时间复杂度为O(n)。使用for循环的效果与此方法差不多。悲伤的表情 - BMiner
204
这段话的意思是:这不仅遍历了对象,还创建了一个包含所有键的全新数组,因此它完全无法回答问题。 - GetFree
46
在Chrome 25上看起来比使用for循环要快得多:http://jsperf.com/count-elements-in-object - fserb
54
为什么有这么多赞?从编码的角度来看,这绝对是最快的方法。不需要额外的方法或库。在代码速度方面,显然也不错。完全不是失败。 - Andrew
显示剩余24条评论

164
你可以使用以下代码:

你可以使用这段代码:

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;

4
(Object.prototype.hasOwnProperty.call(obj, k)) 的目的是什么? - styfle
16
如果你使用for循环来迭代对象的属性,你也会得到原型链上的属性。这就是为什么需要检查hasOwnProperty的原因。它只返回在对象本身上设置的属性。 - Renaat De Muynck
15
为了更简单,你可以直接写 obj.hasOwnProperty(k)(我在原帖中实际上就是这么写的,但后来更新了)。hasOwnProperty 可以在每个对象上使用,因为它是 Object 原型的一部分,但在极其罕见的情况下,如果该方法被删除或覆盖,则可能会得到意外的结果。通过从 Object.prototype 调用它,使它更加健壮。使用 call 的原因是因为你想要在 obj 上调用该方法而不是在原型上调用。 - Renaat De Muynck
6
使用这个版本不是更好吗? https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/keys - Xavier Delamotte
1
@XavierDelamotte 您说得完全正确。虽然我的版本可以工作,但它非常基础,只是作为一个例子。Mozilla的代码更加安全。(PS:您的链接也在被接受的答案中) - Renaat De Muynck
显示剩余9条评论

142

如果你正在使用Underscore.js,你可以使用_.size感谢Douwe):

_.size(obj)

或者你也可以使用_.keys,这可能更清晰一些:

_.keys(obj).length

我强烈推荐使用Underscore.js。它是一个紧凑的库,可以完成许多基本任务。尽可能地,他们与ECMAScript 5相匹配,并延迟到本地实现。
否则,我支持Avi Flax' answer。我编辑了它,添加了一个链接到MDC文档,其中包括您可以添加到非ECMAScript 5浏览器的keys()方法。

9
如果你使用underscore.js,那么你应该使用_.size代替。好处在于,如果你从数组切换到对象或者反之,结果仍然相同。 - douwe
2
据我了解,Lodash通常比Underscore更好(尽管它们做类似的事情)。 - Merlyn Morgan-Graham
1
@MerlynMorgan-Graham 如果我没记错的话,lodash 最初是 underscore 的一个分支... - molson504x
6
在这种情况下,我认为最好使用_.keys(obj).length,因为返回对象有时是一个纯字符串,其中没有属性。 _.size(obj)会返回字符串的长度,而_.keys(obj).length则返回0。 - Jacob Stamm
2
O(n)复杂度。Lodash和Underscore在内部使用Object.keys。如果未定义Object.keys,Underscore还会将每个键复制到for..in循环中的数组中。 - N. Kudryavtsev
简而言之...“在这里使用整个第三方库来完成一个基本任务,顺便说一下,没有性能好处。” - undefined

97
标准的Object实现(ES5.1 Object Internal Properties and Methods)不需要一个Object来跟踪它的键/属性数量,因此没有标准的方法可以在不显式或隐式地迭代其键的情况下确定Object的大小。因此,这里是最常用的替代方案:

1. ECMAScript的Object.keys()

Object.keys(obj).length;通过内部迭代键来计算临时数组并返回其长度。
  • 优点 - 可读性好,语法简洁。除非本机支持不可用,否则不需要库或自定义代码,只需要一个shim。
  • 缺点 - 由于创建数组而产生的内存开销。

2. 基于库的解决方案

在这个主题的其他基于库的例子中,这些例子在其库的上下文中是有用的习语。然而,从性能的角度来看,与完美的无库代码相比,所有这些库方法实际上都封装了一个for循环或ES5 Object.keys(本机或shimmed)没有任何收益。

3. 优化for循环

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

否则,您的代码可以通过使k成为局部变量(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;

无论在给定的环境中是否更快,这都是一个基准测试问题。无论如何,预计性能提升非常有限。

为什么 var k in myobj 会提高性能?据我所知,只有函数在 JavaScript 中声明新作用域。难道 in 循环是这个规则的例外吗? - Lars Gyrup Brink Nielsen
1
这样做更快吗? for (var k in myobj) hasOwn.call(myobj, k) && ++count; 即用简单的&&替换if语句? - Hamza Kubba
1
你可以使用 Object.getOwnPropertyNames(obj).length 来获取对象属性数量,这样更简单。 - Wilt
@HamzaKubba:逻辑上来说,它们是完全等价的;我怀疑任何JS引擎都不会优化它们。 for(var k in myobj)count += myobj.hasOwnProperty(k);极小可能会更好,因为它将一个逻辑测试和分支替换为无分支(在不可预测分支的意义上)的代码,但这完全取决于引擎是否付出任何代价从bool转换为int,以及在不是自有属性时是否无条件写入“count”。但至少在Edge 113上的测试中,使用+=速度略慢。 - ShadowRanger
仅供自己了解,hasOwn.call(myobj, k) && ++countif (hasOwn.call(myobj, k)) ++count 稍微慢一点。这是微不足道的差异(并且很容易被计时抖动所影响),但它绝对不是更好的选择。 - ShadowRanger

94

这里有三种方法的性能测试结果;

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

Object.keys().length

每秒 20,735 次操作

它非常简单兼容且运行速度快,但是代价高昂,因为它会创建一个新的键数组,然后抛弃它。

return Object.keys(objectToRead).length;

循环遍历键值

每秒15,734次

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

虽然速度稍慢,但内存使用远低于对象,所以如果您想要针对移动设备或其他小型机器进行优化,这可能更好。

使用 Map 代替 Object

每秒 953,839,338 次操作

return mapToRead.size;

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


23
“使用 Map 而不是 Object” - 这是此页面上最有帮助的建议。 - Roy Tinker
MDN有一个有用的比较ObjectMap的页面:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map。缺点似乎是缺少对`JSON.stringify`和`JSON.parse`的本地支持。包装器最终将`Map`-> Object转换为JSON函数,因此它在仅存在于序列化/反序列化的内容方面失去了意义。如果不需要序列化和实际对象功能,则Map似乎是更优秀的选择。 - OXiGEN
很遗憾,看起来jsperf已经崩溃了。如果它再次恢复,有没有可能有人能将基准代码编辑到答案中?我尝试在另一个网站(jsbench.me)上进行复制(虽然没有登录,我无法分享测试),使用一个带有26个键的单个固定对象进行测试,对于小写字母az,在Edge 113上运行在一个薄客户端设置上(因此工作是在相当强大但共享的服务器上完成的),Object.keys().length的吞吐量比for循环高出5倍以上,并且比任何使用hasOwnProperty(缓存或未缓存,分支或不分支)的for循环变体高出10倍以上。 - ShadowRanger
使用Map而不是Object.keys().length解决方案的吞吐量几乎是后者的两倍,因此排名没有改变,但成功程度有很大不同;Map更好,但只是大约2倍,并非50倍。Object.keys().lengthfor循环好两倍,不仅仅是吞吐量高出三分之一。当然,我的微基准测试(重复使用相同的对象/Map)可能会让JS引擎在不应该优化时进行优化,而相对性能显然取决于对象/Map的大小(因为除了Map解决方案外,所有解决方案都是O(n))。 - ShadowRanger
你应该提供不同n的测量值。 - user877329

36

如果您实际上遇到了性能问题,我建议将向对象添加/删除属性的调用包装在一个函数中,该函数还会适当命名(尺寸?)属性进行递增/递减。

您只需要计算初始属性数量一次,然后继续。如果没有实际的性能问题,请不要费心。只需将代码的那部分包装在一个名为getNumberOfProperties(object)的函数中,完成它。


6
因为他提供了一个解决方案。 - crush
@crush 这个答案似乎建议了一些要做的事情,而不是直接给出解决方案。 - hitautodestruct
6
@hitautodestruct提出了一个解决方案:使用add/remove方法递增/递减封装的计数。下面还有一个完全相同的答案,唯一的区别是Confusion没有提供任何代码。回答并不必须提供代码解决方案。 - crush
@crush 你说得对,但它们不应该以一个问题开头。已修复 :) - hitautodestruct
2
这可能不是完美的,但与其他“答案”相比,对于某些情况来说,这可能是最好的解决方案。 - d.raev
2
到目前为止,这是我看到的唯一具有O(1)常数时间性能复杂度的解决方案,因此它是唯一可以回答“不迭代”的问题细节并因此成为真正被接受的答案的解决方案。大多数其他答案如果不是全部都没有回答这个问题,因为它们提供了O(n)线性时间性能复杂度;对于调用类似.keys()函数的一行解决方案也是如此,因为这样的函数调用是O(n)的。 - cellepo

30

这适用于数组和对象

//count objects/arrays
function count(obj){
        return Object.keys(obj).length
     }

用循环计算对象/数组的数量

function count(obj){
        var x=0;
        for(k in obj){
          x++;
         }
      return x;
     }

计算对象/数组或字符串的长度

function count(obj){
        if (typeof (obj) === 'string' || obj instanceof String)
        {
          return obj.toString().length;   
        }
        return Object.keys(obj).length
     }

30
作为之前的回答所述:Object.keys(obj).length 但是:由于ES6中现在有一个真正的Map类,我建议使用它来替代使用对象的属性。suggest
const map = new Map();
map.set("key", "value");
map.size; // THE fastest way

1
什么具体的答案?你能提供链接吗(普通链接,而非裸链接)? - Peter Mortensen
1
我只是在参考 Object.keys(obj).length https://dev59.com/tXVC5IYBdhLWcg3w-mZO#4889658 - Flavien Volken

20

正如 Avi Flax所说

Object.keys(obj).length

使用 Object.keys 可以处理对象上的所有可枚举属性,但如果您想包括不可枚举属性,可以使用 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具有相同的浏览器支持。

然而,在大多数情况下,您可能不想在这些类型的操作中包含非枚举属性,但了解差异总是好的 ;)


2
点赞提到 Object.getOwnPropertyNames,你是这里唯一的一个... - Wilt

17
为了迭代阅读 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来计算文件中有多少个函数)

还有,是的,我在我的示例中使用了Node.js的_Underscore.js模块。

文档可以在这里找到,以及其在GitHub上的源代码和各种其他信息。

最后,Lodash实现:https://lodash.com/docs#size

_.size(obj)


针对您关于 Array(obj).length 的评论: 它不起作用。 http://jsfiddle.net/Jhy8M/ - Jamie Chong
是的,我进一步研究了一下,如果可能的话,我将删除这个答案或者完全编辑它。 - Belldandu
1
我并没有看到这与函数有任何关系,而且在Chrome中我根本没有看到这种行为。我怀疑这可能与Object.defineProperty():enumerable的默认行为有关,即false,尽管我还没有找到任何关于var obj = { a: true, b: true }如何与var obj = {}; obj.a = true; obj.b = true;不同或者是否采用了W3的不同解释/语义的文档。 - Nolo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接