JavaScript/ES6:如何从类中返回多个迭代器?

3

我正在实现一个双向链表作为编程练习的一部分,并且希望允许开发人员使用 for...in 符号来向前和向后遍历它的节点。

在最基本的情况下,数据结构如下所示:

class DoublyLinkedList {
    constructor(data) {
        if (data) {
            this.head = new DoublyLinkedListNode(data)
        } else {
            this.head = null
        }
    }

    append = (data) => {
        if (!this.head) {
            this.prepend(data)
        } else {
            const newTail = new DoublyLinkedListNode(data)
            let current = this.head
            while(current.next) {
                current = current.next
            }

            current.next = newTail
            newTail.prev = current
        }
    }
}

接下来,我添加了生成器函数:

    *values() {
        let current = this.head
        while (current) {
            yield current.data;
            current = current.next;
        }
    }

    *valuesBackward() {
        let currentForwards = this.head
        while (currentForwards.next) {
            currentForwards = currentForwards.next
        }

        const tail = currentForwards
        let currentBackwards = tail
        while (currentBackwards) {
            yield currentBackwards.data
            currentBackwards = currentBackwards.prev
        }
    }

我能够添加一个单一的前向迭代器,只需将以下内容添加到类中:

[Symbol.iterator]() { return this.values()}

我尝试将以下两个内容添加到类中:

iterateForward = () => [Symbol.iterator] = () => this.valuesBackward()
iterateBackward = () => [Symbol.iterator] = () => this.valuesBackward()

然后尝试使用 for (node in list.iterateForward()) 迭代,但是这会导致错误 TypeError: undefined is not a function

我猜这个错误可以从代码中看出来,所以下一步我尝试了:

    iterateForward = () => {
        const vals = this.values()

        const it = {
            [Symbol.iterator]() {
                return vals()
            }
        }
        return it
    }

这并没有报错,但是迭代没有生效 - 迭代器零次运行。

我缺少什么?有可能实现我想要的吗?

1个回答

5

这些东西经常让我感到困惑,因此这里有一个摘要,我们可以共同参考。

背景

iterable 是一个具有 [Symbol.iterator] 属性的对象,当您将该属性作为函数调用时,它会返回一个迭代器对象。

迭代器对象具有一个 .next() 属性,每次调用该函数时,它都会返回带有预期属性 {value: x, done: false} 的对象。迭代器对象通常将迭代的状态保存在这个单独的对象中(因此迭代器可以彼此独立)。

因此,为了支持多个迭代器,您需要创建多个方法,每个方法返回一个不同的可迭代对象,每个可迭代对象都有自己的 [Symbol.iterator],当调用它时,返回一个不同的迭代器对象。

因此,总结一下,您需要:

  1. 调用返回可迭代对象的方法
  2. 可迭代对象是一个具有 [Symbol.iterator] 属性并具有访问原始数据的对象。
  3. 当您在该 [Symbol.iterator] 属性中调用函数时,会获得一个迭代器对象。
  4. 迭代器对象包含一个 .next() 方法,该方法通过每次调用 .next(),将您迭代序列中的每个项目传递给您,并返回像这样的对象 {value: x, done: false}

您可以跳过步骤1,直接让您的核心对象具有 [Symbol.iterator] 属性。这实际上成为了您的默认迭代。如果您这样做:

for (let x of myObj) {
    console.log(x);
}

如果你想要让一个对象变成可迭代的,需要在它的[Symbol.iterator]属性上创建一个 iterator 函数。然后当你要遍历这个可迭代的对象时,就会调用myObj[Symbol.iterator]()。但是,如果你想有多种不同的方式来迭代集合,那么你可以创建返回它们自己可迭代对象(具有自己的[Symbol.iterator]属性)的各个函数。


在数组中,你可以使用.entries().values()两种方法,分别返回不同的迭代器,从而得到不同的可迭代对象。

let x = ['a', 'b', 'c'];

for (let v of x.values()) {
    console.log(v);                
}

这将会输出:

'a'
'b'
'c'

或者,对于.entries()
let x = ['a', 'b', 'c'];

for (let v of x.entries()) {
    console.log(v);                
}

[0, "a"]
[1, "b"]
[2, "c"]

因此,.values().entries() 分别返回一个不同的对象,每个对象都有不同的 [Symbol.iterator] 属性,当调用该属性时,会返回一个唯一序列的迭代器函数。
在数组的情况下,.values() 返回一个函数,当调用该函数时,会给你与直接迭代数组完全相同的迭代器(例如,数组本身上的 [Symbol.iterator] 属性)。
现在,针对您的具体情况:
您想创建两种方法,比方说 .forward().backward(),它们各自创建一个带有 [Symbol.iterator] 属性的对象,当调用该属性时,返回其唯一的迭代器对象。
因此,obj.forward() 将返回一个带有 [Symbol.iterator] 属性的对象,当调用该属性时,返回具有适当 .next() 属性以向前迭代和适当起始状态的迭代器对象。
因此,obj.backward() 将返回一个带有 [Symbol.iterator] 属性的对象,当调用该属性时,返回具有适当 .next() 属性以向后迭代和适当起始状态的迭代器对象。
以下是使用数组的示例:

class myArray extends Array {
    forward() {
        return this;    // core object already has appropriate forward
                        // [Symbol.iterator] property, so we can use it
    }
    backward() {
        return {
            [Symbol.iterator]: () => {
                let i = this.length - 1;      // maintains state in closure
                return {
                    next: () => {             // get next item in iteration
                        if (i < 0) {
                            return {done: true};
                        } else {
                            return {value: this[i--], done: false};
                        }
                    }
                }
            }
        }
        
    }
}


let x = new myArray('a', 'b', 'c');

console.log("Forward using default iterator")
for (let v of x) {
    console.log(v);
}

console.log("\nUsing .forward()")
for (let v of x.forward()) {
    console.log(v);
}

console.log("\nUsing .backward()")
for (let v of x.backward()) {
    console.log(v);
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接