简体   繁体   English

在Javascript中迭代对象属性的最快方法是什么?

[英]What's the fastest way to iterate over an object's properties in Javascript?

I know that I can iterate over an object's properties like this: 我知道我可以像这样遍历对象的属性:

for (property in object)
{
    // do stuff
}

I also know that the fastest way to iterate over an array in Javascript is to use a decreasing while loop: 我也知道在Javascript中迭代数组的最快方法是使用递减的while循环:

var i = myArray.length;
while (i--)
{
    // do stuff fast
}

I'm wondering if there is something similar to a decreasing while loop for iterating over an object's properties. 我想知道是否存在类似于减少对象的属性的while循环的方法。

Edit: just a word about the answers concerned with enumerability - I'm not. 编辑:只是一个关于可枚举的答案的词-我不是。

UPDATE 2018 / TLDR; 更新2018 / TLDR;

Apparently, someone took my idea to the next level, and used it to speed up "summing over an object's properties" by over 100x across the browser spectrum - find his jsperf here : 显然,有人将我的想法带入了一个新的层次,并用它来在整个浏览器范围内将“对象的属性总和”加速超过100倍- 在这里找到他的jsperf

在此处输入图片说明

The pink bar represents his "pre-compiled sum" approach which just leaves all the other approaches and operations in the dust. 粉色条代表他的“预编译总和”方法,该方法只剩下所有其他方法和操作。

What is the trick? 诀窍是什么?

His code does this: 他的代码这样做:

var x = 0;
x += o.a;
x += o.b;
x += o.c;
// ...

which is way faster than this: 比这快得多的方法:

var x = 0;
for (var key in o) {
  x += o[key];
}

...especially if the order in which we access the properties ( a , b , c ) matches the order in o 's hidden class . ...特别是如果我们访问属性( abc )的顺序与o隐藏类中的顺序匹配。

Long explanation follows: 详细解释如下:

Faster object property loops 更快的对象属性循环

Let me start by saying, for ... in loops are just fine, and you only want to think of this in performance-critical code with a lot of CPU and RAM usage. 首先, for ... in循环很好,而您只想在具有大量CPU和RAM占用率的对性能要求很高的代码中考虑这一点。 Usually, there is more important stuff you should spend your time on. 通常,您应该花更多时间在重要的事情上。 However, if you are a performance-freak, you might be interested in this near-perfect alternative: 但是,如果您是个性能怪胎,您可能会对这种近乎完美的选择感兴趣:

Javascript Objects Javascript对象

Generally, there are two use-cases for JS objects: 通常,JS对象有两个用例:

  1. "Dictionaries", aka "associative arrays" are general containers with a varying set of properties , indexed by string keys. “字典”(也称为“关联数组”)是具有不同属性集的常规容器, 这些属性由字符串键索引。
  2. "Objects of constant type" (for which the so-called hidden class is always the same) have a fixed set of properties of fixed order . “常量类型的对象”(所谓的隐藏类始终相同)具有一组固定顺序的固定属性 Yes! 是! - While the standard does not guarantee any order, modern VM implementations all do have a (hidden) order, to speed things up. -尽管标准不保证任何顺序,但是现代VM实现都具有(隐藏)顺序,以加快处理速度。 It will be crucial to always maintain that order, as we explore later. 稍后我们将继续探讨,始终保持该顺序至关重要。

Using "objects of constant type" instead of "dictionary types" is generally a whole lot faster because the optimizer understands the structure of these objects. 通常,使用“常量类型的对象”代替“字典类型”的速度要快得多,因为优化程序可以理解这些对象的结构。 If you are curious as to how to achieve that, you might want to check out Vyacheslav Egorov's blog which sheds a whole lot of light on how V8 but also other Javascript run-times, work with objects. 如果您对如何实现这一目标感到好奇,则可以查看Vyacheslav Egorov的博客 ,该博客充分说明了V8和其他Javascript运行时如何与对象一起使用。 Vyacheslav explains Javascript's object property look-up implementation in this blog entry . Vyacheslav在此博客条目中解释了Javascript的对象属性查找实现

Looping over an object's properties 遍历对象的属性

The default for ... in is certainly an Ok choice to iterate over all properties of objects. for ... in的默认值当然是可以遍历对象的所有属性的确定选择。 However, for ... in might treat your object as a dictionary with string keys, even if it has a hidden type. 但是, for ... in可能会将您的对象视为具有字符串键的字典,即使该对象具有隐藏类型也是如此。 In that case, in every iteration you have the overhead of a dictionary lookup, which is often implemented as a hashtable lookup . 在这种情况下,每次迭代都需要字典查找的开销,而字典查找通常被实现为哈希表查找 In many cases, the optimizer is smart enough to avoid that, and performance is on par with constant naming of your properties, but it is simply not guaranteed. 在许多情况下,优化器足够聪明,可以避免这种情况,并且性能与属性的恒定命名是一致的,但不能保证。 Often enough, the optimizer can't help you, and your loop will run a whole lot slower than it should. 通常,优化器无法为您提供帮助,并且循环运行的速度将大大慢于应有的速度。 The worst thing is though that sometimes that is unavoidable, especially if your loop gets more complex. 最糟糕的是,尽管有时这是不可避免的,尤其是在循环变得更加复杂的情况下。 Optimizers are just not that smart (yet!). 优化器还不那么聪明(但!)。 The following pseudocode describes how for ... in works in slow mode: 以下伪代码描述了for ... in在慢速模式下的工作方式:

for each key in o:                                // key is a string!
    var value = o._hiddenDictionary.lookup(key);  // this is the overhead
    doSomethingWith(key, value);

An unrolled, un-optimized for ... in loop, looping over an object with three properties ['a', 'b', 'c'] of given order, looks like this: 展开,未优化for ... in循环,在具有给定顺序的三个属性['a','b','c']的对象上循环,如下所示:

var value = o._hiddenDictionary.lookup('a');
doSomethingWith('a', value);
var value = o._hiddenDictionary.lookup('b');
doSomethingWith('b', value);
var value = o._hiddenDictionary.lookup('c');
doSomethingWith('c', value);

Assuming that you cannot optimize doSomethingWith , Amdahl's law tells us that you can gain a lot of performance if and only if: 假设您无法优化doSomethingWith ,则阿姆达尔定律告诉我们,只有在以下情况下,您才能获得很多性能:

  1. doSomethingWith is very fast already (when compared to the cost of the dictionary lookup) and doSomethingWith已经非常快了(与字典查找的成本相比),并且
  2. you can actually get rid of that dictionary lookup overhead. 您实际上可以摆脱字典查找的开销。

We can indeed get rid of that lookup using, what I call, a pre-compiled iterator , a dedicated function that iterates over all objects of a fixed type, ie a type with a fixed set of properties of fixed order, and performing a specific operation on all of them. 实际上,我们可以使用预编译的迭代器摆脱这种查找,所谓的预编译迭代器是一种专用函数,该函数可对固定类型的所有对象(即具有固定顺序的一组固定属性的类型进行迭代)并执行特定的对所有这些进行操作。 That iterator explicitly calls a callback (let's call it doSomethingWith ) on each of your properties by their proper name. 该迭代器通过每个属性的专有名称显式调用一个回调(将其称为doSomethingWith )。 As a result, the run-time can always make use of the type's hidden class , without having to rely on promises by the optimizer. 结果,运行时始终可以利用类型的隐藏类 ,而不必依赖优化器的承诺。 The following pseudocode describes how the pre-compiled iterator works for any object with the three properties ['a', 'b', 'c'] in given order: 以下伪代码描述了预编译的迭代器如何对具有三个属性['a', 'b', 'c']的给定顺序的任何对象进行工作:

doSomethingWith('a', o.a)
doSomethingWith('b', o.b)
doSomethingWith('c', o.c)

There is no overhead. 没有开销。 We don't need to look anything up. 我们不需要查找任何内容。 The compiler already can trivially compute the exact memory address of each of the properties, using the hidden type information, and it even uses the most cache-friendly order of iteration. 编译器已经可以使用隐藏的类型信息来轻松计算每个属性的确切内存地址,甚至可以使用对缓存最友好的迭代顺序。 This is also (very very close to) the fastest code you can get with for...in and a perfect optimizer. 这也是(非常非常接近)最快的代码,你可以用得到for...in和完美的优化。

Performance Test 性能测试

初步业绩

This jsperf shows that the the pre-compiled iterator approach is quite a bit faster than the standard for ... in loop. 这张jsperf展示了预编译的迭代器方法比for ... in循环的标准for ... in快得多。 Note though that the speed-up largely depends on how the object is created and on the complexity of the loop. 但是请注意,提速很大程度上取决于对象的创建方式以及循环的复杂性。 Since this test only has very simple loops, you sometimes might not observe much of a speed-up. 由于此测试仅具有非常简单的循环,因此您有时可能不会观察到太多加速。 However, in some of my own tests, I was able to see a 25x speed-up of the pre-compiled iterator; 但是,在我自己的一些测试中,我能够看到预编译迭代器的速度提高了25倍。 or rather a significant slow-down of the for ... in loop, because the optimizer was not able to get rid of the string-lookups. 或者说for ... in循环的显着减慢,因为优化器无法摆脱对字符串的查找。

With more tests coming in , we can draw some first conclusions on different optimizer implementations: 随着更多测试的到来 ,我们可以得出关于不同优化器实现的一些初步结论:

  1. The pre-compiled iterator is generally performing a whole lot better, even in very simple loops. 即使在非常简单的循环中,预编译的迭代器通常也会表现出更好的性能。
  2. In IE, the two approaches show the least variance. 在IE中,这两种方法的差异最小。 Bravo Microsoft for writing a decent iteration optimizer (at least for this particular problem)! Bravo Microsoft编写了不错的迭代优化器(至少针对此特定问题)!
  3. In Firefox, for ... in is the slowest by a huge margin. 在Firefox中, for ... in是最慢的。 The iteration optimizer does not do a good job over there. 迭代优化器在那边做得不好。

However, the tests have a very simple loop body. 但是,测试的循环主体非常简单。 I am still looking for a test case where the optimizer can never achieve constant indexing, across all (or almost all) browsers. 我仍在寻找一个测试用例,其中优化器永远无法在所有(或几乎所有)浏览器上实现恒定索引。 Any suggestions are very welcome! 任何建议都非常欢迎!

Code

JSFiddle here . JSFiddle在这里

The following compileIterator function pre-compiles an iterator for any type of (simple) object (disregarding nested properties, for now). 以下compileIterator函数可为任何类型的(简单)对象预编译一个迭代器( compileIterator不考虑嵌套属性)。 The iterator needs a bit of extra information, representing the exact type of all objects it should iterate over. 迭代器需要一些额外的信息,以表示应该迭代的所有对象的确切类型。 Such type information can generally be represented as an array of string property names, of the exact order, which the declareType function takes to create a convenient type object. 这种类型信息通常可以表示为字符串属性名称的数组,其顺序完全相同, declareType函数用来创建方便的类型对象。 If you want to see a more complete example, refer to the jsperf entry . 如果要查看更完整的示例,请参考jsperf条目

//
// Fast object iterators in JavaScript.
//

// ########################################################################
// Type Utilities (define once, then re-use for the life-time of our application)
// ########################################################################

/**
  * Compiles and returns the "pre-compiled iterator" for any type of given properties.
  */
var compileIterator = function(typeProperties) {
  // pre-compile constant iteration over object properties
  var iteratorFunStr = '(function(obj, cb) {\n';
  for (var i = 0; i < typeProperties.length; ++i) {
    // call callback on i'th property, passing key and value
    iteratorFunStr += 'cb(\'' + typeProperties[i] + '\', obj.' + typeProperties[i] + ');\n';
  };
  iteratorFunStr += '})';

  // actually compile and return the function
  return eval(iteratorFunStr);
};

// Construct type-information and iterator for a performance-critical type, from an array of property names
var declareType = function(propertyNamesInOrder) {
  var self = {
    // "type description": listing all properties, in specific order
    propertyNamesInOrder: propertyNamesInOrder,

    // compile iterator function for this specific type
    forEach: compileIterator(propertyNamesInOrder),

    // create new object with given properties of given order, and matching initial values
    construct: function(initialValues) {
      //var o = { _type: self };     // also store type information?
      var o = {};
      propertyNamesInOrder.forEach((name) => o[name] = initialValues[name]);
      return o;
    }
  };
  return self;
};

And here is how we use it: 这是我们的用法:

// ########################################################################
// Declare any amount of types (once per application run)
// ########################################################################

var MyType = declareType(['a', 'b', 'c']);


// ########################################################################
// Run-time stuff (we might do these things again and again during run-time)
// ########################################################################

// Object `o` (if not overtly tempered with) will always have the same hidden class, 
// thereby making life for the optimizer easier:
var o = MyType.construct({a: 1, b: 5, c: 123});

// Sum over all properties of `o`
var x = 0;
MyType.forEach(o, function(key, value) { 
  // console.log([key, value]);
  x += value; 
});
console.log(x);

JSFiddle here . JSFiddle在这里

1) There are many different ways to enumerate properties: 1)枚举属性有很多不同的方法:

  • for..in (iterates over enumerable properties of the object and its prototype chain) for..in (迭代对象及其原型链的无数属性)
  • Object.keys(obj) returns the array of the enumerable properties, found directly on the object (not in its prototype chain) Object.keys(obj)返回直接在对象上找到的可枚举属性的数组(不在其原型链中)
  • Object.getOwnPropertyNames(obj) returns an array of all properties (enumerable or not) found directly on the object. Object.getOwnPropertyNames(obj)返回直接在对象上找到的所有属性(可枚举与否)的数组。
  • If you're dealing with multiple objects of the same "shape" (set of properties), it might make sense to "pre-compile" the iteration code (see the other answer here ). 如果要处理具有相同“形状”(属性集)的多个对象,则“预编译”迭代代码可能很有意义(请参见此处的其他答案 )。
  • for..of can't be used to iterate an arbitrary object, but can be used with a Map or a Set , which are both suitable replacements for ordinary Objects for certain use-cases. for..of不能用于迭代任意对象,但可以与MapSet ,它们对于某些用例都是普通Object的适当替代。
  • ... ...

Perhaps if you stated your original problem, someone could suggest a way to optimize. 也许如果您说出了最初的问题,那么有人可以建议一种优化方法。

2) I find it hard to believe that the actual enumeration is taking more than whatever you do with the properties in the loop body. 2)我发现很难相信实际的枚举比对循环体中的属性所做的更多。

3) You didn't specify what platform you're developing for. 3)您没有指定要开发的平台。 The answer would probably depend on it, and the available language features depend on it too. 答案可能取决于它,可用的语言功能也取决于它。 Eg in SpiderMonkey (Firefox JS interpreter) circa 2009 you could use for each(var x in arr) ( docs ) if you actually needed the values, not the keys. 例如,大约在2009年的SpiderMonkey(Firefox JS解释器)中,如果您实际需要的是值而不是键,则可以使用for each(var x in arr)docs )。 It was faster than for (var i in arr) { var x = arr[i]; ... } 它比for (var i in arr) { var x = arr[i]; ... } for (var i in arr) { var x = arr[i]; ... } . for (var i in arr) { var x = arr[i]; ... }

V8 at some point regressed the performance of for..in and subsequently fixed it . V8在某个时候降低for..in的性能,然后对其进行了修复 Here's a post on the internals of for..in in V8 in 2017: https://v8project.blogspot.com/2017/03/fast-for-in-in-v8.html 这是2017年V8中for..in内部的帖子: https : //v8project.blogspot.com/2017/03/fast-for-in-in-v8.html

4) You probably just didn't include it in your snippet, but a faster way to do a for..in iteration is to make sure the variables you use in the loop are declared inside the function containing the loop, ie: 4)您可能只是没有在代码段中包含它,但是在for..in迭代中执行for..in一种更快的方法是确保在循环中使用的变量在包含循环的函数中声明,即:

//slower
for (property in object) { /* do stuff */ }

//faster
for (var property in object) { /* do stuff */ }

5) Related to (4): while trying to optimize a Firefox extension I once noticed that extracting a tight loop into a separate function improved its performance ( link ). 5)与(4)相关:在尝试优化Firefox扩展时,我曾经注意到将紧密循环提取到单独的函数中可以改善其性能( link )。 (Obviously, it doesn't mean you should always do that!) (显然,这并不意味着您应该始终这样做!)

You could alternatively use Object.getOwnPropertyNames to get the keys of the object. 您也可以使用Object.getOwnPropertyNames来获取对象的键。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames

var obj = {a:"a",b:"b"}
///{a: "a", b: "b"}
var keys = Object.getOwnPropertyNames(a)
///(2) ["a", "b"]

Explicit use of Iterator in JavaScript 1.7+ might be faster or slower. 在JavaScript 1.7+中明确使用Iterator可能更快或更慢。 Of course this will only iterate an object's own properties. 当然,这只会迭代对象本身的属性。 The catch statement also might be faster with ex instanceof StopIteration replaced with ex === StopIteration . 使用ex instanceof StopIteration替换为ex === StopIteration ,catch语句也可能会更快。

var obj = {a:1,b:2,c:3,d:4,e:5,f:6},
   iter = new Iterator(obj, true);

while (true) {
    try {
        doSomethingWithProperty(iter.next());
    } catch (ex if (ex instanceof StopIteration)) {
        break;
    }
}

The for/in loop is the best way to enumerate properties of a Javascript object. for/in循环是枚举Javascript对象的属性的最佳方法。 It should be understood that this will only loop through "enumerable" properties, and in no particular order. 应该理解的是,这只会循环通过“可枚举”的属性,并且没有特定的顺序。 Not all properties are enumerable. 并非所有属性都是可枚举的。 All properties/methods added programmatically via your own Javascript code will be enumerable, but predefined properties/methods that are inherited (such as toString ) are not usually enumerable. 通过您自己的Javascript代码以编程方式添加的所有属性/方法都是可枚举的,但是继承的预定义属性/方法(例如toString )通常是不可枚举的。

You can check enumerability like so... 您可以像这样检查可枚举性...

var o = new Object();
alert(o.propertyIsEnumerable("toString"));

If you don't know the names of the properties, for..in is a good way to enumerate them. 如果您不知道属性的名称,那么for..in是枚举它们的好方法。 If you do, you're better off with explicit dereference. 如果这样做,则最好使用显式取消引用。

Object.keys() coupled with the traditonal for loop to iterate over the keys, and lookup for the values out performs all other techniques. Object.keys()与传统的for循环结合使用以遍历键,并通过查找值来执行所有其他技术。 Here is a comprehensive performance comparison. 这是全面的性能比较。

https://gists.cwidanage.com/2018/06/how-to-iterate-over-object-entries-in.html https://gists.cwidanage.com/2018/06/how-to-iterate-over-object-entries-in.html

An object's properties are unordered by definition. 根据定义,对象的属性是无序的。 The lack of order means that there is no "forwards", and therefore there is no "backwards" either. 缺乏秩序意味着没有“前进”,因此也没有“前进”。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM