javascript - que - Cómo hacer un iterador de una clase ES6
what is function*in javascript (6)
Explicación
Hacer que un objeto sea
iterable
significa que este objeto tiene un método nombrado con el
Symbol.iterator
.
Cuando se llama a este método, debe devolver una interfaz llamada
iterador
.
Este
iterador
debe tener un método
next
que devuelva el siguiente resultado.
Este resultado debe ser un objeto con una propiedad de
value
que proporcione el siguiente valor y una propiedad de
done
, que debe ser
true
cuando no haya más resultados y
false
caso contrario.
Implementación
También implementaré un iterador para una clase llamada
Matrix
que todos los elementos oscilarán entre
0
y
width * height - 1
.
MatrixIterator
una clase diferente para este iterador llamada
MatrixIterator
.
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;
}
}
Observe que
Matrix
implementa el protocolo
iterador
definiendo el símbolo
Symbol.iterator
.
Dentro de este método, se crea una instancia de
MatrixIterator
que toma
this
, es decir, la instancia de
Matrix
como parámetro, y dentro de
MatrixIterator
, se define el
next
método.
Particularmente me gusta esta forma de implementar un iterador porque muestra claramente el
iterador
y la implementación de
Symbol.iterator
.
Alternativamente, tampoco se puede definir directamente
Symbol.iterator
, y en su lugar agregar una función al
prototype[Symbol.iterator]
siguiente manera:
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};
}
}
Ejemplo de uso
Matrix.prototype[Symbol.iterator] = function() {
return new MatrixIterator(this);
};
¿Cómo haría un iterador de una clase ES6 de la misma manera que la sintaxis
SomeClass.prototype.__iterator__ = function() {...}
?
[EDITAR 16:00]
Los siguientes trabajos:
class SomeClass {
constructor() {
}
*[Symbol.iterator]() {
yield ''1'';
yield ''2'';
}
//*generator() {
//}
}
an_instance = new SomeClass();
for (let v of an_instance) {
console.log(v);
}
WebStorm flags
*[Symbol.iterator]()
con una advertencia de ''nombre de función esperado'' directamente después del asterisco, pero de lo contrario, esto se compila y funciona bien con Traceur.
(Nota: WebStorm no genera ningún error para
*generator()
.
Aquí hay un ejemplo para iterar sobre una clase personalizada de matriz 2D en ES6
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;
}
}
}
}
El uso de tal clase sería
let matrix = new Matrix();
for (let cell of matrix) {
console.log(cell)
}
Lo que daría salida
1
2
9
5
3
8
4
6
7
Defina un método iterador adecuado. Por ejemplo:
class C {
constructor() { this.a = [] }
add(x) { this.a.push(x) }
[Symbol.iterator]() { return this.a.values() }
}
Editar: Uso de muestra:
let c = new C
c.add(1); c.add(2)
for (let i of c) console.log(i)
Documentación: iterator
Clase de ejemplo que implementa tanto el protocolo iterador como las técnicas de protocolo iterable :
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();
}
}
Acceda a los elementos utilizando cualquiera de las técnicas:
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);
Ejemplo de una clase de iterador ES6 que se almacena en un subobjeto:
let matrix = new Matrix(3, 2);
for (let e of matrix) {
console.log(e);
}
Symbol.iterator
especificar la propiedad
Symbol.iterator
para
SomeClass
que devuelve el
iterator
para las instancias de clase.
El iterador debe tener el método
next()
, que a su vez devuelve el objeto con los campos
done
y
value
.
Ejemplo simplificado:
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) }
}
};
};
O usando clases ES6 y funciones de flecha:
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) })
};
};
}
Y uso:
var obj = new SomeClass();
for (var i of obj) { console.log(i) }
En su pregunta actualizada, se dio cuenta del
iterador de
clase a través de la
función de generador
.
Puede hacerlo, pero debe comprender que el iterador NO PODRÍA SER un generador.
En realidad, el iterador en es6 es cualquier objeto que tiene un
método
next()
específico