Можно ли реализовать отложенный итератор для ключей объекта в ES5 JavaScript без одновременной загрузки всех ключей объекта в память?

#javascript #arrays #object #iterator #generator

#javascript #массивы #объект #итератор #генератор

Вопрос:

Я хочу написать функцию ES5 JavaScript (т. Е. без генераторов или Symbol.iterator ), которая выполняет то, что делает следующая функция генератора в ES6:

 function *keys(o) {
  for (let key in o)
    yield key
}
  

Я хочу вернуть отложенный итератор без одновременной загрузки всех ключей в память, so Object.keys не учитывается, поскольку он возвращает массив ключей. Однако, похоже, я не могу понять, как это сделать.

Я был в отчаянии, поэтому я начал изучать, как генераторы переносятся в версии JavaScript, которые их не поддерживают. Если вы введете описанную выше функцию генератора в Facebook Regenerator, вы получите следующий результат:

 var _marked =
/*#__PURE__*/
regeneratorRuntime.mark(keys);

function keys(o) {
  var key;
  return regeneratorRuntime.wrap(function keys$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.t0 = regeneratorRuntime.keys(o);

        case 1:
          if ((_context.t1 = _context.t0()).done) {
            _context.next = 7;
            break;
          }

          key = _context.t1.value;
          _context.next = 5;
          return key;

        case 5:
          _context.next = 1;
          break;

        case 7:
        case "end":
          return _context.stop();
      }
    }
  }, _marked, this);
}
  

После прочтения этого вывода я подумал, что реализация regeneratorRuntime.keys может содержать ответ, но, похоже, что эта функция загружает все ключи в память одновременно:

 exports.keys = function(object) {
  var keys = [];
  for (var key in object) {
    keys.push(key);
  }
  keys.reverse();

  // Rather than returning an object with a next method, we keep
  // things simple and return the next function itself.
  return function next() {
    while (keys.length) {
      var key = keys.pop();
      if (key in object) {
        next.value = key;
        next.done = false;
        return next;
      }
    }

    // To avoid creating an additional object, we just hang the .value
    // and .done properties off the next function object itself. This
    // also ensures that the minifier will not anonymize the function.
    next.done = true;
    return next;
  };
};
  

Есть идеи?

Комментарии:

1. Я сомневаюсь, что есть какой-либо способ сделать это. Нет стандартного интерфейса, который позволяет получать указатели на свойства объекта или обращаться к ним через числовой индекс. Это просто академическое упражнение или у вас есть реальная потребность в этом?

2. Это скорее академическое упражнение, но я думаю, что это было бы полезно для написания кода, который преобразует объекты в функциональном стиле без излишнего выделения памяти.

3. Насколько я понимаю, все ключи объекта уже находятся в памяти (пожалуйста, поправьте меня, если я ошибаюсь). Единственная стоимость Object.keys — это создание Array с неглубокой копией ключей (что обычно означает просто Array с достаточным количеством пробелов для #keys указателей, что не так уж много). Ваш движок JS может даже использовать это, чтобы удалить фактический, Array если он никогда не сохранялся / мутировал, получая доступ к ключам напрямую по запросу (не учитывается, но тогда стоимость массива указателей довольно тривиальна). В лучшем случае это кажется преждевременной оптимизацией, основанной на неправильном понимании JS.

4. Я действительно считаю, что ключи объекта уже находятся в памяти. Полагаю, мне следовало спросить, есть ли способ сделать это без выделения массива для хранения ключей, когда я должен был бы иметь возможность просто перебирать их там, где они уже хранятся. Я согласен, что разница в производительности, вероятно, была бы незначительной; мне было просто любопытно.

Ответ №1:

Нет, нет способа создать отложенный итератор свойств. В ES6 были Reflect.enumerate функции и генератор, позволяющие вам написать этот keys помощник, но в ES5 такой возможности не существует — учитывая, что в ES5 не было концепции итератора, это неудивительно.