Подтвердить что ты не робот

Какой самый быстрый способ перебора свойств объекта в Javascript?

Я знаю, что могу перебирать свойства объекта следующим образом:

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

Я также знаю, что самый быстрый способ итерации по массиву в Javascript заключается в использовании цикла while:

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

Мне интересно, есть ли что-то похожее на сокращающийся цикл while для итерации над свойствами объекта.

Изменить: просто слово об ответах, связанных с перечислимостью - я не знаю.

4b9b3361

Ответ 1

1) Существует множество способов перечисления свойств:

  • for..in (итерации по перечислимым свойствам объекта и цепочке его прототипов)
  • Object.keys(obj) возвращает массив перечислимых свойств, найденных непосредственно на объекте (не в его цепочке прототипов).
  • Object.getOwnPropertyNames(obj) возвращает массив всех свойств (перечисляемых или нет), найденных непосредственно на объекте.
  • Если вы имеете дело с несколькими объектами одной и той же "формы" (набор свойств), может иметь смысл "предварительно скомпилировать" итерационный код (см. другой ответьте здесь).
  • for..of не может использоваться для итерации произвольного объекта, но может использоваться с Map или Set, которые являются подходящими заменами для обычных объектов для определенных случаев использования.
  • ...

Возможно, если бы вы заявили о своей первоначальной проблеме, кто-то мог предложить способ оптимизации.

2) Мне трудно поверить, что фактическое перечисление принимает больше, чем все, что вы делаете со свойствами в теле цикла.

3) Вы не указали, на какой платформе вы работаете. Ответ, вероятно, будет зависеть от него, и от него зависят и доступные функции языка. Например. в SpiderMonkey (интерпретатор Firefox JS) около 2009 года вы могли бы использовать for each(var x in arr) (docs), если вам действительно нужны значения, а не ключи. Это было быстрее, чем for (var i in arr) { var x = arr[i]; ... }.

V8 в какой-то момент регрессировал производительность for..in и впоследствии исправил его. Здесь сообщение о внутренних элементах for..in в V8 в 2017 году: https://v8project.blogspot.com/2017/03/fast-for-in-in-v8.html

4) Вероятно, вы просто не включили его в свой фрагмент, но более быстрый способ выполнить итерацию for..in - убедиться, что переменные, которые вы используете в цикле, объявлены внутри функции, содержащей цикл, то есть:

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

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

5) Связано с (4): при попытке оптимизировать расширение Firefox я однажды заметил, что извлечение узкой петли в отдельную функцию улучшило ее производительность (ссылка). (Очевидно, это не значит, что вы всегда должны это делать!)

Ответ 2

Позвольте мне сказать, что петли for ... in все в порядке, и вы хотите только подумать об этом в критическом по производительности коде с большим количеством использования ЦП и ОЗУ. Обычно есть более важные вещи, на которые вы должны потратить свое время. Тем не менее, если вы являетесь игроком производительности, вас может заинтересовать эта почти идеальная альтернатива:

Объекты Javascript

Как правило, для объектов JS существует два варианта использования:

  • "Словари", a.k.a "ассоциативные массивы" - это общие контейнеры с изменяющимся набором свойств, индексированные строковыми ключами.
  • "Объекты постоянного типа" (для которых так называемый скрытый класс всегда один и тот же) имеют фиксированный набор свойств.

Использование "объектов постоянного типа" вместо "типов словарей" обычно происходит намного быстрее, потому что оптимизатор понимает структуру этих объектов. Если вам интересно, как этого достичь, вы можете проверить блог Вячеслава Егорова, который проливает много света на то, как V8, но также и время выполнения Javascript, работа с объектами. Вячеслав объясняет реализацию свойства объекта JavaScript в этой записи в блоге.

Петли по свойствам объекта

По умолчанию for ... in - это, безусловно, выбор Ok для повторения всех свойств объектов. Однако for ... in может обрабатывать ваш объект как словарь со строковыми ключами, даже если он имеет скрытый тип. В этом случае на каждой итерации у вас есть накладные расходы на поиск словаря, который часто реализуется как поиск хеш-таблицы. Во многих случаях оптимизатор достаточно умен, чтобы этого избежать, а производительность наравне с постоянным наименованием ваших свойств, но это просто не гарантируется. Достаточно часто оптимизатор не может вам помочь, и ваш цикл будет работать намного медленнее, чем нужно. Хуже всего то, что иногда это неизбежно, особенно если ваша петля становится более сложной. Оптимизаторы просто не такие умные (пока!). Следующий псевдокод описывает, как for ... in работает в медленном режиме:

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

Развернутый, не оптимизированный цикл for ... in, перебирающий объект с тремя свойствами ['a', 'b', 'c'] заданного порядка, выглядит так:

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

Предполагая, что вы не можете оптимизировать doSomethingWith, Закон Amdahl говорит нам, что вы можете получить большую производительность тогда и только тогда, когда:

  • doSomethingWith работает очень быстро (по сравнению со стоимостью поиска в словаре) и
  • вы действительно можете избавиться от поиска этого словаря.

Мы действительно можем избавиться от поиска в словаре, используя то, что я называю "предварительно скомпилированным итератором", специальную функцию, которая выполняет итерацию по всем объектам фиксированного типа, то есть тип с фиксированным набором свойств фиксированного типа заказ. Эта функция явно вызывает обратный вызов для каждого из ваших свойств по их собственному имени. В результате время выполнения всегда может использовать тип скрытый класс, не полагаясь на promises оптимизатором. Следующий псевдокод описывает, как предварительно скомпилированный итератор работает для любого объекта с тремя свойствами ['a', 'b', 'c'] в указанном порядке:

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

Накладных расходов нет. Нам не нужно ничего искать. Компилятор уже может тривиально вычислить точный адрес памяти каждого из свойств, используя информацию скрытого типа. Это также (очень очень близко) самый быстрый код, который вы можете получить с помощью for...in и идеальный оптимизатор.

Тест производительности

preliminary performance results

Этот jsperf показывает, что предварительно скомпилированный подход итератора довольно немного быстрее, чем стандартный цикл for ... in. Обратите внимание, что ускорение во многом зависит от того, как создается объект и от сложности цикла. Поскольку этот тест имеет очень простые циклы, вы иногда не можете наблюдать большую часть ускорения. Однако в некоторых моих собственных тестах я смог увидеть 25-кратное ускорение предварительно скомпилированного итератора; или, скорее, значительное замедление цикла for ... in, поскольку оптимизатор не смог избавиться от поиска строк.

С больше тестов в, мы можем сделать некоторые первые выводы о различных реализациях оптимизатора:

  • Предварительно скомпилированный итератор, как правило, намного лучше, даже в очень простых циклах.
  • В IE оба подхода показывают наименьшую дисперсию. Bravo Microsoft для написания достойного оптимизатора итераций (по крайней мере для этой конкретной проблемы)!
  • В Firefox for ... in является самым медленным с огромным запасом. Оптимизатор итераций не делает хорошую работу там.

Однако тесты имеют очень простой цикл. Я все еще ищу тестовый пример, когда оптимизатор никогда не может добиться постоянной индексации во всех (или почти всех) браузерах. Любые предложения очень приветствуются!

код

JSFiddle здесь.

Следующая функция compileIterator предварительно компилирует итератор для любого типа объекта (без учета вложенных свойств, пока). Итератору требуется дополнительная информация, представляющая точный тип всех объектов, которые он должен перебирать. Такая информация типа обычно может быть представлена ​​как массив имен свойств строки, точного порядка. Если вы хотите увидеть более полный пример, обратитесь к jsperf entry:

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

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

/**
  * Init #1: Compile iterator function for a specific type.
  */
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);
};

// Init #2: 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 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;
};

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

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


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

// Construct object `o` with matching hidden type:
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 здесь.

Ответ 3

Цикл for/in - лучший способ перечислить свойства объекта Javascript. Следует понимать, что это будет проходить только через "перечислимые" свойства и в определенном порядке. Не все свойства перечислены. Все свойства/методы, добавленные программным путем через ваш собственный код Javascript, будут перечислимы, но предопределенные свойства/методы, которые наследуются (например, toString), обычно не перечислимы.

Вы можете проверить перечислимость так...

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

Ответ 4

Явное использование Iterator в JavaScript 1.7+ может быть более быстрым или медленным. Конечно, это приведет только к переходу объектов собственных. Оператор catch также может быть быстрее с заменой ex instanceof StopIteration на ex === StopIteration.

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

Ответ 5

По определению свойства объекта неупорядочены. Отсутствие порядка означает, что нет "форвардов", и поэтому нет "назад".

Ответ 6

Если вы не знаете имена свойств, for..in - это хороший способ их перечислить. Если вы это сделаете, вам будет лучше с явным разыменованием.