Iterables: Objects with iterators, or generators

佐手、 提交于 2019-12-11 10:47:02

问题


Let's assume two similar implementations of an object with a defined iterator: one iterator using generators, the other using iterables. Both of these two work with Array.from, and both of them can be iterated over. What are the differences in these two approaches, which one is preferred, and why? Is there ever a need for the lesser approach?

class Foo {
  constructor( ...args ) {
    this.f = args;
  }
  [Symbol.iterator]() {
    let c = 0;

    const i = {

      next: () => {
        if ( c < this.f.length ) {
          return {value:this.f[c++], done: false};
        }
        else {
          return {value:undefined,done:true};
        }
      }

    };
    return i;
  }

};

class Bar {
  constructor( ...args ) {
    this.f = args;
  }
  *[Symbol.iterator]() {
    let c = 0;

    if ( c < this.f.length ) {
      yield this.f[c++];
    }
    else {
      return;
    }
  }

};

Here we can test them both to show that they're essentially the same.

var o1 = new Foo([1,2,3]);
for ( let x of o1 ) {
  console.warn(x)
}
console.log(o1, Array.from(o1));

var o2 = new Bar([1,2,3]);
for ( let x of o2 ) {
  console.warn(x)
}
console.log(o2, Array.from(o2));

回答1:


two similar implementations of an object with a defined iterator: one iterator using generators, the other using iterables.

Let's correct the terminology first: You have defined two (constructors for) objects that are Iterables. They are both iterable in the sense that they have have Symbol.iterator method that returns an iterator - an object with a next method. One of these methods is implemented by literally returning an object, the other is implemented using generator syntax.

We can test them both to show that they're essentially the same.

Uh, no, you've made an essential mistake: you've used rest parameters in your constructors, so both of your objects ended up with an array of one array as their f value.

If you used either var o = new FooBar(1, 2, 3) or constructor(args) {, the property would be what you expected and the examples would show that they absolutely don't do the same thing.

So let's fix your code:

class Test {
  constructor(arr) {
    this.f = arr;
  }
}
class Foo extends Test {
  [Symbol.iterator]() {
    let c = 0;
    return {
      next: () => {
        if ( c < this.f.length ) {
          return {value: this.f[c++], done: false};
        } else {
          return {value: undefined, done: true};
        }
      }
    };
  }
}
class Bar extends Test {
  *[Symbol.iterator]() {
    let c = 0;
    while (c < this.f.length) // written a lot nicer using a `for` loop
      yield this.f[c++];
    // return undefined; // we really should omit that
  }
}

for (let test of [Foo, Bar]) {
  console.log(test.name);
  const o = new test([1,2,3]);
  for (const x of o)
    console.log(x)
  console.log(Array.from(o));
}

This now does what you actually wanted.

What are the differences in these two approaches?

I hope it's clear from the above code: generator functions are much simpler.

Which one is preferred, and why?

Make a guess :-) Syntactic sugar improves readability and simplifies complex behaviours through abstraction.

Is there ever a need for the lesser approach?

I can't imagine any standard use case. Of course generator syntax is a feature that needs to be supported by the engine, but so is the complete iteration protocol. Maybe there are some edge cases where a hand-crafted micro-optimised iterator object is faster/cheaper/lighter than a generator, e.g. for constant infinite iterators, but I doubt it.



来源:https://stackoverflow.com/questions/45093404/iterables-objects-with-iterators-or-generators

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!