如何将ES6类转换为一个迭代器。

83

如何以与 JS1.7 中的 SomeClass.prototype.__iterator__ = function() {...} 语法相同的方式,使用 ES6 类创建一个迭代器?

[16:00 编辑]

以下方法可行:

class SomeClass {
    constructor() {
    }

    *[Symbol.iterator]() {
        yield '1';
        yield '2';
    }

    //*generator() {
    //}

}

an_instance = new SomeClass();
for (let v of an_instance) {
    console.log(v);
}

WebStorm在星号后直接添加“函数名期望”的警告标签,但除此之外,这段代码使用Traceur编译和运行没有问题。(请注意,WebStorm对*generator()不会生成任何错误。)


这段代码在我的 v8.1.1 版本的 Node 中运行良好。 - Shaheen Ghiassy
7个回答

72
你需要为SomeClass指定Symbol.iterator属性,该属性返回类实例的迭代器。迭代器必须具有next()方法,该方法返回一个带有donevalue字段的对象。以下是简化示例:
function SomeClass() {
  this._data = [1,2,3,4];
}

SomeClass.prototype[Symbol.iterator] = function() {
  var index = 0;
  var data  = this._data;

  return {
    next: function() {
      return { value: data[++index], done: !(index in data) }
    }
  };
};

或者使用ES6类和箭头函数:

class SomeClass {
  constructor() {
    this._data = [1,2,3,4];
  }

  [Symbol.iterator]() {
    var index = -1;
    var data  = this._data;

    return {
      next: () => ({ value: data[++index], done: !(index in data) })
    };
  };
}

使用方法:

var obj = new SomeClass();
for (var i of obj) { console.log(i) }

在您更新的问题中,您意识到可以通过生成器函数遍历迭代器类。您确实可以这样做,但必须理解迭代器不一定是生成器。实际上,在ES6中,迭代器是具有特定 next() 方法 的任何对象。


3
这实际上很有效,比标记为正确的答案更好。谢谢! - user5900250
为了使ES5代码正常工作,我不得不像ES6代码一样将index = -1设置。可能是个错误吗? - Guerric P
@alexpods,我只是想问一下,我注意到你的完成声明。当在控制台中测试时,它从来没有真实性。我正在测试它。如果它是Python,它实际上会起作用,但我不确定这是否是一个错误? - Fallenreaper
想知道,有了这个自定义迭代器,是否有一种简单的方法将生成的数据直接转换为数组?考虑添加一个类似于“toArray”的方法,该方法在内部调用迭代器并填充数组,但也许还有更好的方法? - vir us
在稀疏数组上,done: !(index in data) 可以提前退出循环。一个简单的修复方法是:done: index == data.length。如果你需要处理会改变数组长度的循环,可以使用 >= 并确保每次迭代都检查 .length 而不是预先分配给一个常量。 - Sideways S

61

定义一个合适的迭代器方法。例如:

class C {
  constructor() { this.a = [] }
  add(x) { this.a.push(x) }
  [Symbol.iterator]() { return this.a.values() }
}

编辑:示例用法:

let c = new C
c.add(1); c.add(2)
for (let i of c) console.log(i)

1
通过外观上的样子,使用星号作为生成器函数的前缀。 - user5321531
21
这个例子并没有使用生成器,它只是将迭代委托给了一个数组迭代器。 - Andreas Rossberg
请问您能否添加一个使用此代码的示例?我无法让它正常工作。 - timkay
3
@timkay 你可能无法使其正常工作,因为大多数实现中数组仍然没有.values()方法。请改用this.a[Symbol.iterator]() - Bergi
1
谢谢!正如@bergi所指出的,大多数实现不支持数组的values()方法。他的解决方案更加通用。也许你可以更新一下你的代码。 - timkay
显示剩余3条评论

28

以下是用 ES6 迭代遍历 2D 矩阵自定义类的示例:

class Matrix {
    constructor() {
        this.matrix = [[1, 2, 9],
                       [5, 3, 8],
                       [4, 6, 7]];
    }

    *[Symbol.iterator]() {
        for (let row of this.matrix) {
            for (let cell of row) {
                yield cell;
            }
        }
    }
}

这样的一个类的使用方式将会是

let matrix = new Matrix();

for (let cell of matrix) {
    console.log(cell)
}

输出结果为

1
2
9
5
3
8
4
6
7

注意:以上代码可能需要最新版本的nodeJS才能正常工作。使用node v8.1.1编译没有问题。 - Shaheen Ghiassy
3
您可以使用for...of循环来简化迭代器:for (let row of this.matrix) { for (let cell of row) { yield cell; } } - Luke Willis
1
@LukeMWillis - 很好!好多了。已更新答案。 - Shaheen Ghiassy

19

文档:迭代协议

实现了迭代器协议可迭代协议技术的示例类:

class MyCollection {
  constructor(elements) {
    if (!Array.isArray(elements))
      throw new Error('Parameter to constructor must be array');

    this.elements = elements;
  }

  // Implement "iterator protocol"
  *iterator() {
    for (let key in this.elements) {
      var value = this.elements[key];
      yield value;
    }
  }

  // Implement "iterable protocol"
  [Symbol.iterator]() {
    return this.iterator();
  }
}

使用任一技术来访问元素:

var myCollection = new MyCollection(['foo', 'bar', 'bah', 'bat']);

// Access elements of the collection using iterable
for (let element of myCollection)
  console.log('element via "iterable": ' + element);

// Access elements of the collection using iterator
var iterator = myCollection.iterator();
while (element = iterator.next().value)
  console.log('element via "iterator": ' + element);

给未来读者注意:在一些 JavaScript shell 上,没有前置的 let element; 的情况下,最后一个带有 while 的循环不能正常工作。否则是很好的答案。 - S0AndS0

8

解释

让一个对象成为可迭代对象意味着该对象具有名为 Symbol.iterator 的方法。当调用此方法时,它应返回一个名为iterator的接口。

这个迭代器必须具有一个next方法,该方法返回下一个结果。这个结果应该是一个带有value属性的对象,该属性提供下一个值,并且应该有一个done属性,当没有更多结果时应该是true,否则为false

实现

我还将为名为Matrix的类实现一个迭代器,其中所有元素的范围将从0width * height - 1。我将为此迭代器创建一个不同的类,名为MatrixIterator

class Matrix {
    constructor(width, height) {
        this.width = width;
        this.height = height;
        this.content = [];

        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                this.content[y * width + x] = y * width + x;
            }
        }
    }

    get(x, y) {
        return this.content[y * this.width + x];
    }

    [Symbol.iterator]() {
        return new MatrixIterator(this);
    }
}


class MatrixIterator {
    constructor(matrix) {
        this.x = 0;
        this.y = 0;
        this.matrix = matrix;
    }

    next() {
        if (this.y == this.matrix.height) return {done: true};

        let value = {
            x: this.x,
            y: this.y,
            value: this.matrix.get(this.x, this.y)
        };

        this.x++;

        if (this.x == this.matrix.width) {
            this.x = 0;
            this.y++;
        }

        return {value, done: false};
    }
}

注意,Matrix 通过定义 Symbol.iterator 符号实现了 iterator 协议。在这个方法内部,创建了一个 MatrixIterator 实例,该实例将 this,即 Matrix 实例作为参数,并且在 MatrixIterator 内部定义了 next 方法。我特别喜欢使用这种方式来实现迭代器,因为它清晰地展示了迭代器和 Symbol.iterator 的实现方式。
另外,也可以不直接定义 Symbol.iterator,而是向 prototype[Symbol.iterator] 添加一个函数,如下所示:
Matrix.prototype[Symbol.iterator] = function() {
    return new MatrixIterator(this);
};

使用示例

let matrix = new Matrix(3, 2);
for (let e of matrix) {
    console.log(e);
}

参考资料 - JavaScript 编程精解 - RamanSB

1

以下是一个存储在子对象中的ES6迭代器类的示例:

class Iterator {
    data;

    constructor(data = {}) {
        this.data = JSON.parse(JSON.stringify(data));
    }

    add(key, value) { this.data[key] = value; }

    get(key) { return this.data[key]; }

    [Symbol.iterator]() {
        const keys = Object.keys(this.data).filter(key => 
        this.data.hasOwnProperty(key));
        const values = keys.map(key => this.data[key]).values();
        return values;
    }
}

1

我认为还没有人发布异步迭代器的示例,所以这里提供一个:

class TableReadStream<T extends any[]> {
    async *[Symbol.asyncIterator]() {
        let row: T
        while((row = await this.readRow()) !== null) {
            yield row
        }
    }
}

使用方法:

for await(let row of readStream) {
    console.log(row)
}

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