A couple of additions to ECMAScript 2015 aren't new built-ins or syntax, but protocols. These protocols can be implemented by any object respecting some conventions.
There are two protocols: The iterable protocol and the iterator protocol.
The iterable protocol allows JavaScript objects to define or customize their iteration behavior, such as what values are looped over in a for..of construct. Some built-in types are built-in iterables with a default iteration behavior, such as Array or Map, while other types (such as Object) are not.
In order to be iterable, an object must implement the @@iterator method, meaning that the object (or one of the objects up its prototype chain) must have a property with a @@iterator key which is available via constant :Symbol.iterator
| Property | Value |
|---|---|
[Symbol.iterator] | A zero arguments function that returns an object, conforming to the iterator protocol. |
Whenever an object needs to be iterated (such as at the beginning of a for..of loop), its @@iterator method is called with no arguments, and the returned iterator is used to obtain the values to be iterated.
The iterator protocol defines a standard way to produce a sequence of values (either finite or infinite).
An object is an iterator when it implements a next() method with the following semantics:
| Property | Value |
|---|---|
next | A zero arguments function that returns an object with two properties:
The |
Some iterators are in turn iterables:
var someArray = [1, 5, 7]; var someArrayEntries = someArray.entries(); someArrayEntries.toString(); // "[object Array Iterator]" someArrayEntries === someArrayEntries[Symbol.iterator](); // true
A String is an example of a built-in iterable object:
var someString = 'hi'; typeof someString[Symbol.iterator]; // "function"
String's default iterator returns the string's code points one by one:
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 } Some built-in constructs, such as the spread syntax, use the same iteration protocol under the hood:
[...someString] // ["h", "i"]
We can redefine the iteration behavior by supplying our own @@iterator:
var someString = new String('hi'); // need to construct a String object explicitly to avoid auto-boxing
someString[Symbol.iterator] = function() {
return { // this is the iterator object, returning a single element, the string "bye"
next: function() {
if (this._first) {
this._first = false;
return { value: 'bye', done: false };
} else {
return { done: true };
}
},
_first: true
};
};
Notice how redefining @@iterator affects the behavior of built-in constructs that use the iteration protocol:
[...someString]; // ["bye"] someString + ''; // "hi"
String, Array, TypedArray, Map and Set are all built-in iterables, because each of their prototype objects implements an @@iterator method.
We can make our own iterables like this:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable]; // [1, 2, 3]
There are many APIs that accept iterables, for example: Map([iterable]), WeakMap([iterable]), Set([iterable]) and 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
Also see Promise.all(iterable), Promise.race(iterable), and Array.from().
Some statements and expressions expect iterables, for example the for-of loops, spread syntax, yield*, and 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"
If an iterable's @@iterator method doesn't return an iterator object, then it's a non-well-formed iterable. Using it as such is likely to result in runtime exceptions or buggy behavior:
var nonWellFormedIterable = {}
nonWellFormedIterable[Symbol.iterator] = () => 1
[...nonWellFormedIterable] // TypeError: [] is not a function
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
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'
// ...
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'
// ...
class SimpleClass {
constructor(data) {
this.index = 0;
this.data = data;
}
[Symbol.iterator]() {
return {
next: () => {
if (this.index < this.data.length) {
return {value: this.data[this.index++], done: false};
} else {
this.index = 0; //If we would like to iterate over this again without forcing manual update of the index
return {done: true};
}
}
}
};
}
const simple = new SimpleClass([1,2,3,4,5]);
for (const val of simple) {
console.log(val); //'0' '1' '2' '3' '4' '5'
}
A generator object is both, iterator and iterable:
var aGeneratorObject = function* () {
yield 1;
yield 2;
yield 3;
}();
typeof aGeneratorObject.next;
// "function", because it has a next method, so it's an iterator
typeof aGeneratorObject[Symbol.iterator];
// "function", because it has an @@iterator method, so it's an iterable
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// true, because its @@iterator method returns itself (an iterator), so it's an well-formed iterable
[...aGeneratorObject];
// [1, 2, 3]
| Feature | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari (WebKit) |
|---|---|---|---|---|---|
| Basic support | 39.0 | 27.0 (27.0) | No support | 26 | 10 |
IteratorResult object instead of throwing | (Yes) | 29.0 (29.0) | No support | (Yes) | 10 |
| Feature | Android | Android Webview | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile | Chrome for Android |
|---|---|---|---|---|---|---|---|
| Basic support | No support | (Yes) | 27.0 (27.0) | No support | No support | 10 | 39.0 |
IteratorResult object instead of throwing | No support | ? | 29.0 (29.0) | No support | No support | ? | (Yes) |
IteratorResult object returned instead of throwingStarting with Gecko 29 (Firefox 29 / Thunderbird 29 / SeaMonkey 2.26), the completed generator function no longer throws a TypeError "generator has already finished". Instead, it returns an IteratorResult object like { value: undefined, done: true } (bug 958951).
Iterator property and @@iterator symbolFrom Gecko 17 (Firefox 17 / Thunderbird 17 / SeaMonkey 2.14) to Gecko 26 (Firefox 26 / Thunderbird 26 / SeaMonkey 2.23 / Firefox OS 1.2) the iterator property was used (bug 907077), and from Gecko 27 to Gecko 35 the "@@iterator" placeholder was used. In Gecko 36 (Firefox 36 / Thunderbird 36 / SeaMonkey 2.33), the @@iterator symbol got implemented (bug 918828).
| Specification | Status | Comment |
|---|---|---|
| ECMAScript 2015 (6th Edition, ECMA-262) The definition of 'Iteration' in that specification. | Standard | Initial definition. |
| ECMAScript Latest Draft (ECMA-262) The definition of 'Iteration' in that specification. | Draft |
© 2005–2018 Mozilla Developer Network and individual contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols