【问题标题】:How to efficiently count the number of keys/properties of an object in JavaScript如何有效地计算 JavaScript 中对象的键/属性的数量
【发布时间】:2010-09-12 16:05:18
【问题描述】:

计算对象的键/属性数量的最快方法是什么?是否可以在不迭代对象的情况下做到这一点?即,不做:

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

(Firefox 确实提供了一个神奇的 __count__ 属性,但它在版本 4 左右被删除了。)

【问题讨论】:

标签: javascript performance properties count key


【解决方案1】:

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

【讨论】:

  • 接受的答案表明这可以完成,并且您没有上下文可以断言没有任何收获。
【解决方案2】:

如果您确实遇到了性能问题,我建议您将向对象添加/删除属性的调用封装在一个函数中,该函数还增加/减少适当命名的(大小?)属性。

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

【讨论】:

  • @hitautodestruct 因为他提供了解决方案。
  • @crush 这个答案似乎是在建议要做的事情,而不是给出直接的解决方案。
  • @hitautodestruct 它建议了一个答案:使用添加/删除方法增加/减少封装的计数。下面还有一个与此完全相同的答案。唯一的区别是,Confusion 没有提供任何代码。答案不强制要求仅提供代码解决方案。
  • 它可能并不完美......但与其他“答案”相比,这可能是某些情况下的最佳解决方案
  • 到目前为止,这是我看到的唯一解决方案,即 O(1) 恒定时间性能复杂度,因此是回答“不迭代”的问题详细信息的唯一解决方案,因此应该是真实的接受的答案。大多数(如果不是所有)其他答案都没有回答这个问题,因为它们提供了 O(n) 线性时间性能复杂度;对于调用类似 .keys() 函数的 1 行解决方案也是如此,因为这样的函数调用是 O(n)。
【解决方案3】:

我不知道有什么方法可以做到这一点。但是,为了将迭代保持在最低限度,您可以尝试检查 __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__ 时,该函数都会触发并重新计算。

【讨论】:

  • 请注意,Object.prototype.__count__ 正在 Gecko 1.9.3 中被删除:whereswalden.com/2010/04/06/…count-property-of-objects-is-being-removed/
  • 现在 Firefox 4 已经发布,这个答案现在已经过时了。 Object.__count__ 不见了,也很好摆脱。
  • 我不会说答案已经过时。将值封装在函数中仍然是一种有趣的策略。
  • 应该使用原型对象来扩展
【解决方案4】:

要在任何ES5-兼容环境中执行此操作,例如Node.js、Chrome、Internet Explorer 9+、Firefox 4+ 或 Safari 5+:

Object.keys(obj).length

【讨论】:

  • 不只是 Node.js,任何支持 ES5 的环境都可以
  • 顺便说一句...刚刚运行了一些测试...此方法在 O(n) 时间内运行。 for 循环并不比这种方法差多少。 ** 悲伤的脸** stackoverflow.com/questions/7956554/…
  • -1(如果可以的话-200)这不仅会遍历对象,还会创建一个包含所有键的全新数组,因此它完全无法回答问题。
  • 这似乎比 for(至少在 Chrome 25 上)要快得多:jsperf.com/count-elements-in-object
  • @GetFree 为什么这么多人竖起大拇指?就编码而言,这绝对是最快的方式。不需要额外的方法或库。就代码速度而言,显然也不算太差。根本不是彻底的失败。 87 个赞对你来说是失败的。
【解决方案5】:

您可以使用以下代码:

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;

【讨论】:

  • 检查(Object.prototype.hasOwnProperty.call(obj, k))的目的是什么?
  • @styfle 如果您使用 for 循环来迭代对象的属性,您还可以获得原型链中的属性。这就是为什么检查hasOwnProperty 是必要的。它只返回对象本身设置的属性。
  • @styfle 为了更简单,你可以写obj.hasOwnProperty(k)(实际上我在原来的帖子中这样做了,但后来更新了)。 hasOwnProperty 在每个对象上都可用,因为它是 Object 原型的一部分,但在极少数情况下,此方法会被删除或覆盖,您可能会得到意想不到的结果。通过从Object.prototype 调用它,它会变得更加健壮。之所以使用call,是因为你想在obj而不是原型上调用方法。
  • 使用这个版本不是更好吗? developer.mozilla.org/en-US/docs/JavaScript/Reference/…
  • @XavierDelamotte 你是绝对正确的。虽然我的版本有效,但它非常基本,并以示例为例。 Mozilla 的代码更安全。 (PS:您的链接也在接受的答案中)
【解决方案6】:

如果您使用Underscore.js,您可以使用_.size (thanks douwe):

_.size(obj)

您也可以使用_.keys,这对某些人来说可能更清楚:

_.keys(obj).length

我强烈推荐 Underscore.js。这是一个紧凑的库,可以做很多基本的事情。只要有可能,它们就会匹配 ECMAScript 5 并遵循本机实现。

否则我支持Avi Flax' answer。我对其进行了编辑以添加指向 MDC 文档的链接,其中包含可以添加到非 ECMAScript 5 浏览器的 keys() 方法。

【讨论】:

  • 如果你使用 underscore.js 那么你应该使用 _.size 代替。好消息是,如果您以某种方式从数组切换到对象,反之亦然,结果保持不变。
  • 据我了解,lodash 通常比下划线更好(尽管它们做类似的事情)。
  • @MerlynMorgan-Graham 如果我没记错的话,lodash 最初是下划线的分叉...
  • _.keys(obj).length 最适合我,因为我的返回对象有时是一个没有属性的纯字符串。 _.size(obj) 返回字符串的长度,而 _.keys(obj).length 返回 0。
  • O(n) 复杂度。 Lodash 和 Underscore 在内部使用 Object.keys。如果未定义 Object.keys,下划线还会将每个键复制到 for..in 循环内的数组中。
【解决方案7】:

我解决此问题的方法是构建我自己的基本列表实现,该列表记录对象中存储了多少项目。这很简单。像这样的:

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

【讨论】:

  • 有趣的选择。这确实消除了聚合计数函数的开销,但代价是每次添加或删除元素时都会调用函数,不幸的是,这可能会更糟。与普通数组相比,我个人会使用这样的列表实现来进行数据封装和它可以提供的自定义方法,但不是在我只需要快速项目计数时。
  • 我喜欢你的回答,但我也是一只旅鼠并点击了投票。这提出了一个有趣的困境。您没有在说明中考虑某些行为,例如我已经对您的答案投赞成票的情况,但随后我被指示“点击投票”并且不能。该指令无声地失败,但我从你的内容中收集到,所以无声地失败并不是你喜欢你的代码做的事情。只是提醒一下。
  • 我真的很喜欢这个答案,很好的数据结构。如果 add 上的函数调用对性能有影响,那么如果必须迭代一个对象,性能就会大大提高。这应该允许最快的循环模式var i = basiclist.countwhile(i--){...}
  • 基本清单不应该至少包括基本检查吗?就像检查 add 是否替换旧项目或 remove 是否使用不存在的索引调用。如果undefined 是有效的项目值,也无法检查列表是否具有给定索引。
  • 列表应该是有序且可迭代的。数据存储在一个对象中,因此不能保证元素的顺序。你如何找到一个有洞的列表的长度? this.count?最高指数值?如果您在同一索引处添加两个项目,则计数将进入错误状态。
【解决方案8】:

对于那些在他们的项目中包含 Underscore.js 的人,您可以这样做:

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

或功能风格:

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

【讨论】:

  • 问题不是关于其他方法,而是关于最快的方法。你建议使用库,它本身已经在性能上失败了。
【解决方案9】:

对于那些在他们的项目中有Ext JS 4 的人,你可以这样做:

Ext.Object.getSize(myobj);

这样做的好处是它适用于所有与 Ext JS 兼容的浏览器(包括Internet Explorer 6 - Internet Explorer 8)。但是,我相信运行时间并不比 O(n) 好,与其他建议的解决方案一样。

【讨论】:

    【解决方案10】:

    如果之前答案中的 jQuery 不起作用,请尝试

    $(Object.Item).length
    

    【讨论】:

    • 好像Object.Item不存在
    • 特别是什么答案?您可以链接到其中的部分或全部吗?
    • 好的,用户 codejoecode 已离开大楼。也许是其他人?
    【解决方案11】:

    作为stated by Avi Flax

    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
    

    作为stated here,它与Object.keys具有相同的浏览器支持。

    但是,在大多数情况下,您可能不想在这些类型的操作中包含不可枚举,但了解它们的区别总是好的;)

    【讨论】:

    • 点赞Object.getOwnPropertyNames,你是这里唯一的人...
    【解决方案12】:

    标准的 Object 实现 (ES5.1 Object Internal Properties and Methods) 不需要 Object 来跟踪其键/属性的数量,因此不应该有标准的方法来确定 Object 的大小而无需显式或隐式迭代它的钥匙。

    所以这里是最常用的替代方案:

    1。 ECMAScript 的 Object.keys()

    Object.keys(obj).length; 通过内部迭代键来计算临时数组并返回其长度。

    • 优点 - 可读且简洁的语法。如果本机支持不可用,则不需要任何库或自定义代码,除了 shim
    • 缺点 - 由于创建数组而导致的内存开销。

    2。基于库的解决方案

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

    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 中声明了新的作用域。循环内是这个规则的例外吗?
    • 这样更快吗? for (var k in myobj) hasOwn.call(myobj, k) && ++count; 即用简单的 && 替换 if 语句?
    • 你可以做的最后一件事是:Object.getOwnPropertyNames(obj).length;简单得多。
    【解决方案13】:

    要在Avi Flax' answer 上进行迭代,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 模块。

    可以在here 上找到文档以及它在 GitHub 上的来源和各种其他信息。

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

    _.size(obj)

    【讨论】:

    • 回复你的cmets关于Array(obj).length:它不起作用。 http://jsfiddle.net/Jhy8M/
    • 是的,我仔细研究了一下,如果可能的话,我最终会删除这个答案,或者只是一起编辑它
    • 我没有看到这与函数有任何关系,一方面,在 Chrome 中我根本看不到这种行为。我怀疑这可能与Object.defineProperty():enumerable 的默认行为有关,即false,尽管我还没有找到任何关于var obj = { a: true, b: true } 可能与var obj = {}; obj.a = true; obj.b = true; 有何不同的文档,或者只是如果不同的解释/ W3 的语义已被 Chrome 采用。
    【解决方案14】:

    来自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
    

    注意:在Internet Explorer 9之前的浏览器中不起作用。

    【讨论】:

    • 如果你要扩展内置原型或填充一个属性(即猴子补丁),请正确:为了向前兼容,先检查属性是否存在,然后再制作属性不可枚举,这样构造对象的自己的键就不会被污染。对于方法,请使用 actual methods。我的建议:关注these examples,它演示了如何添加一个尽可能接近内置方法的方法。
    【解决方案15】:

    如上一个答案中的回答:Object.keys(obj).length

    但是:由于我们现在在 ES6 中有一个真正的 Map 类,我会 suggest 使用它而不是使用对象的属性。

    const map = new Map();
    map.set("key", "value");
    map.size; // THE fastest way
    

    【讨论】:

    【解决方案16】:

    您可以使用:

    Object.keys(objectName).length; 
    

    Object.values(objectName).length;
    

    【讨论】:

      【解决方案17】:

      我尝试使其对所有对象都可用,如下所示:

      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
      

      【讨论】:

      【解决方案18】:

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

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

      【讨论】:

        【解决方案19】:

        以下是对三种方法的一些性能测试;

        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;
        

        它稍微慢一些,但与内存使用量相差甚远,因此如果您有兴趣针对移动设备或其他小型机器进行优化,它可能会更好。

        使用地图代替对象

        每秒 953,839,338 次操作

        return mapToRead.size;
        

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

        【讨论】:

        • “使用地图代替对象” - 这是本页最有用的建议。
        猜你喜欢
        • 2011-12-18
        • 2012-01-03
        • 1970-01-01
        • 2010-11-23
        • 1970-01-01
        • 2019-04-24
        • 2022-01-10
        相关资源
        最近更新 更多