【问题标题】:Get current value from an iterator从迭代器中获取当前值
【发布时间】:2018-07-14 07:32:06
【问题描述】:

我正在研究 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.log(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.log(i.current()); // 0

虽然我可以只使用类(甚至是普通的旧函数),但我想知道这是否可以使用生成器/迭代器来完成,或者是否有更好的选择。

【问题讨论】:

    标签: javascript iterator generator


    【解决方案1】:

    您的生成器函数的问题是 a) 当您调用它时它不会开始运行,它只是创建生成器(this.arr 和 this.index 在第一次调用 next() 之前不会被初始化) 和 b) 无法像使用 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。第二个示例在第一次调用 .next() 时返回 { done: true, value: undefined }。
    • @phippu 糟糕,感谢您的测试。你是对的,第一个并没有真正的意义,我已经换了一个更好的解决方案。
    【解决方案2】:

    这个问题似乎有多种解释。我的理解是,您需要一个迭代器,它提供一种访问最近检索到的值的方法,如最终代码块中的最后一行所示:

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

    这样做不是迭代器接口的一部分,也不是生成器函数提供的。您可以提供一个迭代器包装器来完成它,然后在生成器函数的生成器上使用它(尽管您不需要生成器来执行您正在做的事情,但标准数组迭代器会这样做),请参阅 cmets:

    // 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.log("current", it.current());             // undefined
    console.log("next", it.next());                   // {value: 1, done: false}
    console.log("current", it.current());             // 1
    console.log("currentResult", it.currentResult()); // {value: 1, done: false}
    
    // Example use #2: Just normal use of an iterator
    for (const value of currentWrapper(a)) {
      console.log(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.log("current", it.current());             // undefined
    console.log("next", it.next());                   // {value: 1, done: false}
    console.log("current", it.current());             // 1
    console.log("currentResult", it.currentResult()); // {value: 1, done: false}
    console.log("currentIndex", it.currentIndex());   // 0
    console.log("next", it.next());                   // {value: 2, done: false}
    console.log("current", it.current());             // 2
    console.log("currentResult", it.currentResult()); // {value: 2, done: false}
    console.log("currentIndex", it.currentIndex());   // 1
    
    // Example use #2: Just normal use of an iterator
    for (const value of currentWrapper(a)) {
      console.log(value);
    }
    .as-console-wrapper {
      max-height: 100% !important;
    }

    【讨论】:

      【解决方案3】:

      为什么不使用来自MDN Iterators and generators 的函数,其中只有返回部分被替换为值而不是具有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.log(it.next());
      console.log(it.next());
      console.log(it.last());
      console.log(it.next());

      【讨论】:

      • 我认为目标是拥有一个具有附加功能(访问当前 [最后检索])值的迭代器。
      • 也许可以,但开销很大。
      猜你喜欢
      • 2022-11-20
      • 2018-06-01
      • 2015-10-03
      • 2017-10-30
      • 1970-01-01
      • 2021-05-09
      • 1970-01-01
      • 2012-10-17
      • 1970-01-01
      相关资源
      最近更新 更多