網站搜尋

性能 - 如何在 JavaScript 中高效地計算物件的鍵/屬性數量


計算一個物件的鍵/屬性數量最快的方法是什麼?有可能在不遍歷物件的情況下做到這一點嗎,也就是說,不做以下操作:

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

(Firefox 確實提供了一個魔法 __count__ 屬性,但這在大約第 4 版時被移除了。)

答案#1

要在任何 相容於 ES5 的環境 中進行此操作,例如 Node.js、Chrome、Internet Explorer 9+、Firefox 4+ 或 Safari 5+:

Object.keys(obj).length
  • 瀏覽器相容性
  • Object.keys 文件說明(包含可新增至非 ES5 瀏覽器的方法)

答案#2

你可以使用這段代碼:

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;

答案#3

如果你正在使用 Underscore.js,你可以使用 _.size(感謝 douwe):

_.size(obj)

或者你也可以使用 _.keys,對某些人來說可能更清楚:

_.keys(obj).length

我強烈推薦 Underscore.js。這是一個用於執行許多基本操作的精簡函式庫。只要可能,它們會遵循 ECMAScript 5 並使用原生實現。

否則我支持 Avi Flax 的回答。我編輯了它,添加了一個指向 MDC 文件的鏈接,其中包括你可以添加到非 ECMAScript 5 瀏覽器的 keys() 方法。

答案#4

這裡有三種方法的性能測試;

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

Object.keys().length

每秒 20,735 次操作

Object.keys 非常簡單、兼容且運行快速 但 開銷很大,因為它會創建一個新的鍵數組,然後被丟棄。

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 會追蹤自己的大小,所以我們只是返回一個數字欄位。這比任何其他方法都快得多。如果你能控制這個物件,請將它們轉換為 Map。

答案#5

標準的 Object 實作(ES5.1 Object 內部屬性與方法)不要求 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;

在特定環境下這是否更快,取決於基準測試。無論如何,只能期待非常有限的性能提升。

答案#6

如果你真的遇到性能問題,我建議將添加/移除屬性到/從對象的調用包裝在一個函數中,該函數也會增加/減少一個適當命名的(大小?)屬性。

你只需要計算一次初始的屬性數量,然後從那裡繼續。如果實際上沒有性能問題,就不必費心。只需將那段代碼包裝在一個函數 getNumberOfProperties(object) 中就可以了。

答案#7

如先前回答中所述:Object.keys(obj).length

但是:由於我們現在在 ES6 中有了一個真正的 Map 類,我建議使用它,而不是使用對象的屬性。

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

答案#8

這適用於陣列和物件

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

答案#9

正如 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

如此處所述,這與 Object.keys 擁有相同的瀏覽器支援。

然而,在大多數情況下,你可能不想在這類操作中包含不可列舉的屬性,但了解它們之間的區別總是好的 ;)

答案#10

為了對 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)

答案#11

我不知道有任何方法可以做到這一點。不過,為了將迭代次數保持在最低,你可以嘗試檢查 __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__,這個函數都會觸發並重新計算。