Estás leyendo la versión en inglés del artículo porque aún no existe una traducción para este idioma. ¡Ayúdanos a traducir este artículo!
El protocolo iterable
El protocolo iterable le permite a los objetos en JavaScript definir o personalizar su comportamiento de iteración, como por ejemplo qué valores son iterados dentro de una sentencia for..of
. Algunos objetos nativos, como Array
o Map
, tienen un comportamiento de iteración por defecto, mientras otros objetos (como por ejemplo Object
) no.
Para ser iterable, un objeto debe implementar el método @@iterator, lo cual significa que el objeto (o uno de los objetos dentro de su cadena de prototipos) debe tener una propiedad con un identificador Symbol
.iterator
:
Propiedad | Valor |
---|---|
[Symbol.iterator] |
Una función sin argumentos que retorna un objeto, de acuerdo al protocolo iterador. |
Siempre que un objeto necesite ser iterado (como al comienzo de un for..of
loop), su método @@iterator
es llamado sin argumentos, y el iterador retornado es usado para obtener los valores a ser iterados.
El protocolo iterador
El protocolo iterador define una forma estándar que permite producir una secuencia de valores (sean estos finitos o infinitos).
Un objeto es un iterador cuando este implementa un método next()
con la siguiente semántica:
Propiedad | Valor |
---|---|
next |
Una función sin argumentos que retorna un objeto con dos propiedades:
|
Algunos iteradores son a su vez iterables:
var someArray = [1, 5, 7]; var someArrayEntries = someArray.entries(); someArrayEntries.toString(); // "[object Array Iterator]" someArrayEntries === someArrayEntries[Symbol.iterator](); // true
Ejemplos de protocolos de iteración
Un String
es un ejemplo de un objeto iterable nativo:
var someString = "hi"; typeof someString[Symbol.iterator]; // "function"
Para objetos String
su iterador por defecto retorna cada uno de sus caracteres, uno a la vez:
var iterator = someString[Symbol.iterator](); iterator + ""; // "[object String Iterator]" iterator.next(); // { value: "h", done: false } iterator.next(); // { value: "i", done: false } iterator.next(); // { value: undefined, done: true }
En algunas estructuras nativas del lenguaje como en el caso del operador de propagación spread operator, el mismo protocolo de iteración está presente en su parte interna:
[...someString] // ["h", "i"]
Podemos redefinir el comportamiento de iteración creando nuestro propio @@iterator
:
// es necesario el uso de un objeto creado a partir de la función constructora String, // ya que al usar un string primitivo el auto-boxing generaría una referencia temporal // a un iterador que luego es descartado en el unbox var someString = new String("hi"); someString[Symbol.iterator] = function() { return { // este es el objeto iterador que retorna un único elemento, la cadena string "bye" next: function() { if (this._first) { this._first = false; return { value: "bye", done: false }; } else { return { done: true }; } }, _first: true }; };
Nótese que al redefinir un @@iterator
se puede afectar el comportamiento de construcciones nativas que usan el protocolo de iteración:
[...someString]; // ["bye"] someString + ""; // "hi"
Ejemplos de iterables
Iterables nativos
String
, Array
, TypedArray
, Map
y Set
son objetos iterables nativos, ya que en su objeto prototipo existe un método @@
iterator.
Iterables personalizados
Podemos crear nuestros propios iterables de la siguiente manera:
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable]; // [1, 2, 3]
APIs nativas que aceptan iterables
Existen varios APIs que aceptan iterables, como en el caso de: Map([iterable])
, WeakMap([iterable])
, Set([iterable])
y WeakSet([iterable])
:
var myObj = {}; new Map([[1,"a"],[2,"b"],[3,"c"]]).get(2); // "b" new WeakMap([[{},"a"],[myObj,"b"],[{},"c"]]).get(myObj); // "b" new Set([1, 2, 3]).has(3); // true new Set("123").has("2"); // true new WeakSet(function*() { yield {}; yield myObj; yield {}; }()).has(myObj); // true
De igual manera Promise.all(iterable)
, Promise.race(iterable)
, y Array.from()
.
Sintaxis que espera un iterable
Algunas declaraciones y expresiones esperan iterables, por ejemplo el bucle for-of
, el operador de propagación spread operator, la expresión Yield*
, y la asignación desestructurada destructuring assignment.
for(let value of ["a", "b", "c"]){ console.log(value); } // "a" // "b" // "c" [..."abc"]; // ["a", "b", "c"] function* gen(){ yield* ["a", "b", "c"]; } gen().next(); // { value:"a", done:false } [a, b, c] = new Set(["a", "b", "c"]); a // "a"
Iterables mal definidos
Un método @@iterator
iterable que no retorne un objeto iterador no está correctamente definido, por lo tanto al ejecutarlo de esta manera podría resultar en excepciones en tiempo de ejecución y otros errores:
var nonWellFormedIterable = {} nonWellFormedIterable[Symbol.iterator] = () => 1 [...nonWellFormedIterable] // TypeError: [] is not a function
Ejemplos de iteradores
Iterador simple
function makeIterator(array){ var nextIndex = 0; return { next: function(){ return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {done: true}; } }; } var it = makeIterator(['yo', 'ya']); console.log(it.next().value); // 'yo' console.log(it.next().value); // 'ya' console.log(it.next().done); // true
Iterador infinito
function idMaker(){ var index = 0; return { next: function(){ return {value: index++, done: false}; } }; } var it = idMaker(); console.log(it.next().value); // '0' console.log(it.next().value); // '1' console.log(it.next().value); // '2' // ...
Con un generador
function* makeSimpleGenerator(array){ var nextIndex = 0; while(nextIndex < array.length){ yield array[nextIndex++]; } } var gen = makeSimpleGenerator(['yo', 'ya']); console.log(gen.next().value); // 'yo' console.log(gen.next().value); // 'ya' console.log(gen.next().done); // true function* idMaker(){ var index = 0; while(true) yield index++; } var gen = idMaker(); console.log(gen.next().value); // '0' console.log(gen.next().value); // '1' console.log(gen.next().value); // '2' // ...
¿Un objeto generador es un iterador o un iterable?
Un objeto iterador es tanto un iterador como un iterable:
var aGeneratorObject = function*(){ yield 1; yield 2; yield 3; }(); typeof aGeneratorObject.next; // "function", ya que tiene un método next, por lo tanto es un iterador typeof aGeneratorObject[Symbol.iterator]; // "function", ya que tiene un método @@iterator, por lo tanto es un iterable aGeneratorObject[Symbol.iterator]() === aGeneratorObject; // true, ya que su método @@iterator retorna a sí mismo (un iterador), por lo tanto es un iterable bien formado [...aGeneratorObject]; // [1, 2, 3]
Especificaciones
Especificación | Estado | Comentario |
---|---|---|
ECMAScript 2015 (6th Edition, ECMA-262) La definición de 'Iteration' en esta especificación. |
Standard | Definición inicial. |
Temas relacionados
Para información adicional acerca de generadores generators en ES6, puede visitar la página específica sobre este tema.