首页 > 解决方案 > 在 JavaScript 中,一个可迭代对象应该可以重复迭代吗?

问题描述

我发现有些iterable可以重复迭代:

const iterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 3;
    yield 5;
  }
}

console.log([...iterable]);
console.log([...iterable]);
console.log([...iterable]);

虽然有些不能:

function* generatorFn() {
  yield 1;
  yield 3;
  yield 5;
}

const iterable = generatorFn();

console.log([...iterable]);
console.log([...iterable]);
console.log([...iterable]);

是否有一个规则是否应该重复迭代?

我理解为什么它们的行为不同(这是因为第二种情况,当iterable[Symbol.iterator]调用函数时,返回相同的迭代器(它iterable本身。可以尝试iterable[Symbol.iterator]() === iterable并且它会返回true.iterable.next也是一个函数。所以在这种情况下,iterable是一个生成器对象、可迭代对象和迭代器,所有三个)。但我想知道可迭代对象是一种对象类型,是否有明确定义的行为,是否应该或不应该重复迭代。)

标签: javascriptecmascript-6iterable

解决方案


好的,我想我会在评论中总结我们学到的一些东西并添加更多内容,然后通过写下您的具体问题的答案来结束。

[...x]句法

该语法适用于支持接口[...x]的事物。iterables而且,要支持可迭代接口,您所要做的就是支持该Symbol.iterator属性以提供一个(在调用时)返回迭代器的函数。

内置迭代器也是可迭代的

Javascript 中内置的所有迭代器都派生自同一个IteratorPrototype. 迭代器不需要这样做,这是内置迭代器做出的选择。

这个内置IteratorPrototype也是一个Iterable。它支持Symbol.iterator只是做的功能的属性return this。这是按规范

这意味着所有内置的迭代器someSet.values()都将使用该[...x]语法。我不确定为什么这非常有用,但它肯定会导致混淆 Iterable 可以做什么以及 Iterator 可以做什么,因为这些内置迭代器可以作为其中任何一个。

它会导致一些奇怪的行为,因为如果你这样做:

let s = new Set([1,2,3]);
let iter = s.values();    // gets an iterator
let x = [...iter];
let y = [...iter];
console.log(x);
console.log(y);

第二个[...iter]是一个空数组,因为这里只有一个迭代器。事实上,x === y. 因此,第一个let x = [...iter];耗尽了迭代器。它处于静止状态done,无法再次迭代集合。这是因为内置迭代器的这种时髦行为,它们表现为可迭代的,但只是return this. 他们不会创建一个新的迭代器,它可以像您使用实际的可迭代集合时那样再次迭代集合。每次访问时,此集合迭代都会返回一个全新的迭代器s[Symbol.iterator](),如下所示:

let s = new Set([1,2,3]);
let x = [...s];
let y = [...s];
console.log(x);
console.log(y);

普通迭代器不适用[...x]

要成为 Iterator,您需要实现的只是支持该.next()方法并使用适当的对象进行响应。其实这里有一个超级简单的迭代器,符合规范:

const iter = { 
    i: 1, 
    next: function() { 
        if (this.i <= 3) {
            return { value: this.i++, done: false }; 
        } else {
            return { value: undefined, done: true }; 
        } 
    }
}

如果你尝试这样做let x = [...iter];,它会抛出这个错误:

TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))

但是,如果您通过向其添加适当的[Symbol.iterator]属性使其成为 Iterable,它将作为[...iter];

const iter = { 
    i: 1, 
    next: function() { 
        if (this.i <= 3) {
            return { value: this.i++, done: false }; 
        } else {
            return { value: undefined, done: true }; 
        } 
    },
    [Symbol.iterator]: function() { return this; }
}

let x = [...iter];
console.log(x);

然后,它可以工作,[...iter]因为它现在也是一个可迭代的。

发电机

Generator 函数在被调用时会返回一个 Generator 对象。根据规范,该 Generator 对象的行为既是 anIterator又是 an Iterable。故意没有办法判断这个 Iterator/Iterable 是否来自生成器,这显然是故意的。调用代码只知道它是一个Iterator/Iterable,生成器函数只是创建对调用代码透明的序列的一种方法。它像任何其他迭代器一样被迭代。


你的两个迭代器的故事

在您最初的问题中,您展示了两个迭代器,一个重复工作,一个不重复工作。这里有两件事在起作用。

首先,一些迭代器“消耗”它们的序列,并且没有办法只是重复迭代相同的序列。这些将是制造的序列,而不是静态集合。

其次,在您的第一个代码示例中:

const iterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 3;
    yield 5;
  }
}

console.log([...iterable]);
console.log([...iterable]);
console.log([...iterable]);

单独的迭代器

那个iterable是一个iterable。它不是一个迭代器。iterable[Symbol.iterator]()您可以通过调用which is what [...iterable]does来要求它提供一个迭代器。但是,当你这样做时,它会返回一个全新的 Generator 对象,它是一个全新的迭代器。每次调用iterable[Symbol.iterator]()或导致调用时[...iterable],都会得到一个新的不同的迭代器。

你可以在这里看到:

    const iterable = {
      [Symbol.iterator]: function* () {
        yield 1;
        yield 3;
        yield 5;
      }
    }

    let iterA = iterable[Symbol.iterator]();
    let iterB = iterable[Symbol.iterator]();
    
    // shows false, separate iterators on separate generator objects
    console.log(iterA === iterB);      

因此,您正在为每个迭代器创建一个全新的序列。它重新调用生成器函数以获取新的生成器对象。

相同的迭代器

但是,用你的第二个例子:

function* generatorFn() {
  yield 1;
  yield 3;
  yield 5;
}

const iterable = generatorFn();

console.log([...iterable]);
console.log([...iterable]);
console.log([...iterable]);

这不一样。你iterable在这里所说的是我喜欢认为的pseudo-iterable. 它实现了 theIterable和接口,但是当你Iterator要求它时,它每次都返回相同的对象(本身)。因此,每次执行时,它都在同一个迭代器上运行。但是该迭代器已用尽,并且处于您第一次执行后的状态。所以,后两个是空数组。迭代器没有什么可提供的了。Iterator[...iterable][...iterable]done[...iterable][...iterable]

你的问题

是否有一个规则是否应该重复迭代?

并不真地。首先,最终到达done状态的给定迭代器(非无限迭代器)一旦到达done状态就会给出任何结果。根据迭代器的定义。

因此,Iterable表示某种静态序列的 an 是否可以重复迭代取决于Iterator它在每次被询问时提供的迭代器是否是新的和唯一的,我们在上面的两个示例中已经看到,一个Iterable可以去任何一种方式。

它每次都可以生成一个新的、唯一的迭代器,每次都通过序列呈现一个新的迭代。

或者,每次都Iterable可以产生完全相同的结果。Iterator如果这样做,一旦该迭代器进入该done状态,它就会卡在那里。

还请记住,某些 Iterables 代表可能不可重复的动态集合/序列。Set对于 a或 a之类的东西并非如此Map,但更多自定义类型的 Iterables 可能在迭代时本质上“消耗”它们的集合,当它完成时,即使你得到一个新的新迭代器,也不会再有更多的集合了。

想象一个迭代器,它给你一个价值在 1 美元到 10 美元之间的随机金额的代码,并在你每次向迭代器询问下一个值时从你的银行余额中减去它。在某些时候,您的银行余额达到$0并且该迭代器已完成,甚至获得一个新的迭代器仍将不得不处理相同的$0银行余额(没有更多值)。那将是“消耗”值或某些资源并且不可重复的迭代器的示例。

但我想知道 iterable 是一种对象类型,是否有明确定义的行为,它是否应该重复迭代。

不,它是特定于实现的,完全取决于您正在迭代的内容。使用像 aSet或 aMap或 an这样的静态集合Array,您可以获取一个新的迭代器并每次生成一个新的迭代。但是,我所说的 a psuedo-iterable(每次请求时返回相同迭代器的可迭代对象)或迭代时序列被“消耗”的可迭代对象可能无法重复迭代。所以,它可以故意是任何一种方式。没有标准的方法。这取决于正在迭代的内容。

测试你有什么

这里有一些有用的测试可以帮助人们理解一些事情:

// could do a more comprehensive test by calling `obj.next()` to see if
// it returns an appropriate object with appropriate properties, but
// that is destructive to the iterator (consumes that value) 
// so we keep this one non-destructive
function isLikeAnIterator(obj) {
    return typeof obj === "object" && typeof obj.next === "function)";
}

function isIterable(obj) {
    if (typeof obj === "object" && typeof obj[Symbol.iterator] === "function") {
        let iter = obj[Symbol.iterator]();
        return isLikeAnIterator(iter);
    }
    return false;
}

// A pseudo-iterable returns the same iterator each time
// Sometimes, the pseudo-iterable returns itself as the iterator too
function isPseudoIterable(obj) {
   if (isIterable(obj) {
       let iterA = obj[Symbol.iterator]();
       if (iterA === this) {
          return true;
       }
       let iterB = obj[Symbol.iterator]();
       return iterA === iterB;
   }
   return false;
}

function isGeneratorObject(obj) {
    if (!isIterable(obj) !! !isLikeAnIterator(obj) {
        // does not meet the requirements of a generator object
        // which must be both an iterable and an iterator
        return false;
    }
    throw new Error("Can't tell if it's a generator object or not by design");
}

推荐阅读