如何用ES6类制作迭代器


69

如何以与JS1.7SomeClass.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标志*[Symbol.iterator]()在星号后直接带有“预期的功能名称”警告,但否则,此函数可以编译并在Traceur中正常运行。(请注意,WebStorm不会为生成任何错误*generator()。)


这段代码对我来说在节点上工作正常v8.1.1
Shaheen Ghiassy

Answers:


47

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

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

16
此特定示例未使用生成器。它只是委托给数组迭代器。
Andreas Rossberg

您能否添加一个使用此代码的示例?我无法正常工作。
timkay

2
@timkay您可能无法使用它,因为在大多数实现中数组仍然没有.values()方法。使用this.a[Symbol.iterator]()替代
BERGI

1
谢谢!正如@bergi指出的那样,大多数实现都不支持数组的values()。他的解决方案更通用。也许您可以更新代码。
timkay

46

您需要指定Symbol.iterator属性,SomeClass为其返回类实例的迭代器。迭代器必须具有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()


2
实际上,这比标记为正确的答案更好。谢谢!
StJohn3D

为了使ES5代码正常工作,我必须index = -1像在ES6代码中那样进行设置。可能的错误?
Guerric P

我只想问@alexpods,我注意到您已完成发言。在控制台中进行测试时,它从来没有真实性。我正在测试。如果是python,它实际上会工作,但是我不确定这是否是bug?
Fallenreaper

想知道,有了这个自定义迭代器,有没有一种简单的方法可以将生成的数据直接转换成数组?考虑添加类似“ toArray”的方法,该方法在内部调用迭代器并填充数组,但是也许有更好的方法吗?
vir us

23

这是在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

1
@LukeMWillis-太好了!好多了。更新的答案
Shaheen Ghiassy

14

文档:迭代协议

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

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);

给以后的读者注意;while如果没有进行任何后续操作,则last循环本身将无法正常工作let element;……对于某些JavaScript外壳来说,它是正确的。否则,肯定的答案。
S0AndS0 '19

4

说明

使对象可迭代意味着该对象具有一个名为的方法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。在此方法内部,MatrixIterator创建thisMatrix实例,该实例使用,即该实例作为参数,并在 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;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.