性能 - 如何在 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);});}} */
避免此情況的步驟:
不要把你想計算鍵數的函數放在物件裡
使用一個單獨的物件或專門為函數建立一個新的物件(如果你想要使用
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__,這個函數都會觸發並重新計算。
