Получить текущее значение от итератора

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

Точнее, моя неудачная попытка:

function* iterable(arr) {
  this.index = 0;
  this.arr = arr;
  while(this.index < this.arr.length) {
    yield this.arr[this.index++];
  }
}
iterable.prototype.current = function () {
  return this.arr[this.index];
}

const i = iterable([0, 1, 2]);
console.info(i.current()); // TypeError: Cannot read property 'undefined' of undefined

Желаемую функциональность можно реализовать с помощью такого класса (я знаю, что возвращаемые значения от итератора будут такими объектами, как { value: 1, done: false }):

class iterableClass {
  constructor(arr) {
    this.index = 0;
    this.arr = arr;
  }
  get(i) {
    return this.index < arr.length ? this.arr[this.index] : false;
  }
  next() {
    const val = this.get(this.index);
    this.index++;
    return val;
  }
  current() {
    return this.get(this.index);
  }
}
const i = iterableClass([0, 1, 2]);
console.info(i.current()); // 0

Хотя я мог просто работать с классом (или даже с простой старой функцией), мне было интересно, можно ли это сделать с помощью генератора / итератора или, может быть, есть еще лучший вариант.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
2 080
3

Ответы 3

Почему бы не использовать функцию из Итераторы и генераторы MDN, где только возвращаемая часть заменяется значением вместо объекта со свойством value и done

function makeIterator(array) {
    var nextIndex = 0,
        lastValue;

    return {
        next: function() {
            return lastValue = nextIndex < array.length ? array[nextIndex++] : undefined;
        },
        last: function () {
            return lastValue;
        }
    };
}

var it = makeIterator(['yo', 'ya']);
console.info(it.next());
console.info(it.next());
console.info(it.last());
console.info(it.next());

Я думаю, что цель состояла в том, чтобы иметь итератор с дополнительной функцией (доступ к текущему [последнему полученному]) значению.

T.J. Crowder 14.07.2018 09:45

возможно, но с большими накладными расходами.

Nina Scholz 14.07.2018 09:57

Кажется, есть несколько интерпретаций этого вопроса. Насколько я понимаю, вам нужен итератор, который предоставляет способ доступа к самому недавно полученному значению, как показано последней строкой в ​​вашем последнем блоке кода:

console.info(i.current()); // 0

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

// Get the Iterator prototype, which has no global name
const itPrototype = Object.getPrototypeOf(
    Object.getPrototypeOf([][Symbol.iterator]())
);
function currentWrapper(source) {
    // Allow source to be an iterable or an iterator
    if (Symbol.iterator in source) {
        source = source[Symbol.iterator]();
    }
    // Create our wrapper iterator
    const it = Object.create(itPrototype);
    // Remember the last value we saw from `next`
    let current = null;
    // The iterator method
    it.next = () => {
        return current = source.next();
    };
    // Our additional methods
    it.current = () => current && current.value;
    it.currentResult = () => ({...current});
    return it;
}

Это имеет то преимущество, что оно универсально и многократно используется, а не привязано к конкретному итерируемому объекту.

Живой пример:

// Get the Iterator prototype, which has no global name
const itPrototype = Object.getPrototypeOf(
    Object.getPrototypeOf([][Symbol.iterator]())
);
function currentWrapper(source) {
  // Allow source to be an iterable or an iterator
  if (Symbol.iterator in source) {
    source = source[Symbol.iterator]();
  }
  // Create our wrapper iterator
  const it = Object.create(itPrototype);
  // Remember the last value we saw from `next`
  let current = null;
  // The iterator method
  it.next = () => {
    return current = source.next();
  };
  // Our additional methods
  it.current = () => current && current.value;
  it.currentResult = () => ({...current});
  return it;
}

// Something to iterate over
const a = [1, 2, 3];

// Example use #1: Using `current`
const it = currentWrapper(a[Symbol.iterator]());
console.info("current", it.current());             // undefined
console.info("next", it.next());                   // {value: 1, done: false}
console.info("current", it.current());             // 1
console.info("currentResult", it.currentResult()); // {value: 1, done: false}

// Example use #2: Just normal use of an iterator
for (const value of currentWrapper(a)) {
  console.info(value);
}
.as-console-wrapper {
  max-height: 100% !important;
}

Я сосредоточился на бите current, а не на бите index, потому что я думаю об итерациях как о потоках, а не массивах, но я полагаю, что было бы достаточно просто добавить index. Немного сложная часть - когда итератор закончил работу, увеличиваете ли вы индекс при вызове next или нет? Ниже нет:

// Get the Iterator prototype, which has no global name
const itPrototype = Object.getPrototypeOf(
    Object.getPrototypeOf([][Symbol.iterator]())
);
function currentWrapper(source) {
  // Allow source to be an iterable or an iterator
  if (Symbol.iterator in source) {
    source = source[Symbol.iterator]();
  }
  // Create our wrapper iterator
  const it = Object.create(itPrototype);
  // Remember the last value we saw from `next` and the current "index"
  let current = null;
  let index = -1;
  // The iterator method
  it.next = () => {
    // Don't increase the index if "done" (tricky bit)
    if (!current || !current.done) {
      ++index;
    }
    return current = source.next();
  };
  // Our additional methods
  it.current = () => current && current.value;
  it.currentResult = () => ({...current});
  it.currentIndex = () => index;
  return it;
}

// Something to iterate over
const a = [1, 2, 3];

// Example use #1: Using `current`
const it = currentWrapper(a[Symbol.iterator]());
console.info("current", it.current());             // undefined
console.info("next", it.next());                   // {value: 1, done: false}
console.info("current", it.current());             // 1
console.info("currentResult", it.currentResult()); // {value: 1, done: false}
console.info("currentIndex", it.currentIndex());   // 0
console.info("next", it.next());                   // {value: 2, done: false}
console.info("current", it.current());             // 2
console.info("currentResult", it.currentResult()); // {value: 2, done: false}
console.info("currentIndex", it.currentIndex());   // 1

// Example use #2: Just normal use of an iterator
for (const value of currentWrapper(a)) {
  console.info(value);
}
.as-console-wrapper {
  max-height: 100% !important;
}

Проблема с вашей функцией генератора заключается в том, что а) она не запускается, когда вы ее вызываете, она просто создает генератор (this.arr и this.index не будут инициализированы до первого вызова next()) и б) нет возможности доступ к объекту-генератору изнутри функции, как вы пробовали с this.

Вместо этого вы бы хотели

function iterable(arr) {
  const gen = Object.assign(function* () {
    while (gen.index < gen.arr.length) {
      yield gen.arr[gen.index++];
    }
  }(), {
    arr,
    index: 0,
    current() {
      return gen.arr[gen.index];
    },
  });
  return gen;
}

В качестве альтернативы вместо использования синтаксиса генератора вы также можете напрямую реализовать интерфейс Iterator:

function iterable(arr) {
  return {
    arr,
    index: 0,
    current() { return this.arr[this.index]; },
    next() {
      const done = !(this.index < this.arr.length);
      return { done, value: done ? undefined : this.arr[this.index++] };
    },
    [Symbol.iterator]() { return this; },
  };
}

(который, конечно, можно было бы записать и как class)

Я не совсем уверен, как использовать ваши функции, но const x = iterable([1, 2, 3]); x.next(); генерирует TypeError: x.next() is not a function для первого примера. Второй пример возвращает { done: true, value: undefined } при первом вызове .next().

Philipp Gfeller 14.07.2018 10:27

@phippu Ой, спасибо за тестирование. Вы правы, первое не имело смысла, я выбрал лучшее решение.

Bergi 14.07.2018 10:33

Другие вопросы по теме