让我们假设一个具有已定义迭代器的对象的两个类似实现:一个迭代器使用generators,另一个使用iterables。这两个都与Array.from
一起使用,并且它们都可以迭代。这两种方法有哪些区别,哪一种更受欢迎,为什么?是否需要较小的方法?
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;
}
}
};
在这里,我们可以测试它们,以表明它们基本相同。
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));
答案 0 :(得分:5)
具有已定义迭代器的对象的两个类似实现:一个使用生成器的迭代器,另一个使用迭代器。
首先让术语更正:您已经为Iterables定义了两个(构造函数)对象。它们都是可迭代的,因为它们有Symbol.iterator
方法返回迭代器 - 一个带有next
方法的对象。其中一种方法是通过字面返回一个对象来实现的,另一种是使用生成器语法实现的。
我们可以对它们进行测试,以证明它们基本相同。
呃,不,你犯了一个重大错误:你在构造函数中使用了rest参数,所以你的两个对象都以一个数组的数组作为f
值
如果您使用var o = new FooBar(1, 2, 3)
或constructor(args) {
,则该属性将符合您的预期,并且示例将表明他们绝对不会做同样的事情。
因此,让我们修复您的代码:
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));
}
&#13;
现在这就是你真正想要的。
这两种方法有何不同?
我希望从上面的代码中可以清楚地看出:生成器函数更加简单。
哪一个更受欢迎,为什么?
猜测:-)句法糖通过抽象提高可读性并简化复杂的行为。
是否需要较小的方法?
我无法想象任何标准用例。当然,生成器语法是引擎需要支持的功能,但完整的迭代协议也是如此。也许存在一些边缘情况,其中手工制作的微优化迭代器对象比发生器更快/更便宜/更轻,例如,对于恒定的无限迭代器,但我对此表示怀疑。