Iteratation Protocol (Concept)
The Iteration Protocol was introduced in ES6 to create iterable data structures. It enables the use of the for...of loop, spread syntax, and array destructuring assignment for data structures that adhere to the Iteration Protocol.
Iterable Protocol
The Iterable Protocol is a convention where an object conforms to the protocol by implementing a method with the well-known Symbol Symbol.iterator
as its property key. This method, either directly implemented or inherited through the prototype chain, should return an iterator that adheres to the Iteration Protocol upon calling Symbol.iterator.
Example: Checking for iterability
const isIterable = v => v !== null && typeof v[Symbol.iterator] === 'function';
isIterable([]); // -> true
isIterable(''); // -> true
isIterable(new Map()); // -> true
isIterable(new Set()); // -> true
isIterable({}); // -> false
Iterator Protocol
The Iterator Protocol is a convention where an iterator adheres to the protocol by being returned when the Symbol.iterator method of an iterable is called. An iterator should have a next method and return an iterator result object with value and done properties.
Note: Objects that do not implement or inherit the Symbol.iterator method directly are not iterable since calling Symbol.iterator on them returns nothing.
Built-in Iterables
Built-in Iterable | Symbol.iterator Method |
Array | Array.prototype[Symbol.iterator] |
String | String.prototype[Symbol.iterator] |
Map | Map.prototype[Symbol.iterator] |
Set | Set.prototype[Symbol.iterator] |
TypedArray | TypedArray.prototype[Symbol.iterator] |
arguments | arguments.prototype[Symbol.iterator] |
DOM Collection | NodeList.prototype[Symbol.iterator] HTMLCollection.prototype[Symbol.iterator] |
Differences between for...of
and for...in
The for...of loop iterates over iterable objects (those with a Symbol.iterator method) and assigns the iterable's elements to a variable.
The for...in loop, on the other hand, iterates over all enumerable properties of an object's prototype chain, excluding properties with Symbol keys.
Iterable and Array-like Objects
Array-like objects are objects that resemble arrays, allowing access to property values using indices and possessing a length property. However, array-like objects are not iterable if they lack a Symbol.iterator method.
Examples of array-like objects:
const arrayLike = {
0: 1,
1: 2,
2: 3,
length: 3
};
for (let i = 0; i < arrayLike.length; i++) {
console.log(arrayLike[i]); // 1, 2, 3
}
Note: arguments, NodeList, and HTMLCollection are both array-like objects and iterable objects.
Importance of Iteration Protocol
The Iteration Protocol establishes a convention for iterable data structures, offering a standardized interface for developers using constructs like for...of, spread syntax, and array destructuring assignment. It simplifies the process of accessing data elements, eliminating the need for developers to manually handle data traversal.
User-defined Iterables
Implementing User-defined Iterables
Objects that do not inherently adhere to the Iteration Protocol can be made iterable by following the protocol.
Implement the Symbol.iterator method.
Symbol.iterator should return an iterator with a next method.
The next method should return an iterator result object with value and done properties.
Example:
const evenNumbers = {
[Symbol.iterator]() {
let current = 0;
const max = 10;
return {
next() {
current += 2;
return { value: current, done: current >= max };
}
};
}
};
for (const num of evenNumbers) {
console.log(num); // 2 4 6 8 10
}
Function Generating an Iterable
const evenNumbers = (max) => {
let current = 0;
return {
[Symbol.iterator]() {
return {
next() {
current += 2;
return { value: current, done: current >= max };
}
};
}
};
};
const iterable = evenNumbers(10);
const iterator = iterable[Symbol.iterator]();
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 4, done: false }
// ...
Function Generating an Iterable and Iterator
const evenNumbers = (max) => {
let current = 0;
return {
[Symbol.iterator]() { return this; },
next() {
current += 2;
return { value: current, done: current >= max };
}
};
}
const iterable = evenNumbers(10);
for (const num of iterable) {
console.log(num); // 2 4 6 8 10
}
console.log(iterator.next()); // { value: 2, done: false }
// ...
Infinite Iterables and Lazy Evaluation
Lazy evaluation is a technique where data is not generated until it is required. Infinite iterables, as seen in the evenNumbers
example, showcase lazy evaluation by only generating data when needed, avoiding unnecessary data creation.
const evenNumbers = () => {
let current = 0;
return {
[Symbol.iterator]() { return this; },
next() {
current += 2;
return { value: current, done: current >= max };
}
};
}
const iterable = evenNumbers();
for (const num of iterable) {
if (num > 10000) break;
console.log(num); // 2 4 6 8 10 ... 9998 10000
}
const [f1, f2, f3] = evenNumbers();
console.log(f1, f2, f3); // 2 4 6
The evenNumbers
function creates an infinite iterable mechanism, but the data is not generated until needed. This allows for efficient execution, avoiding unnecessary memory consumption and supporting the representation of infinite values.