Custom iteration behavior
Basics of Iteration Protocols
ECMAScript 6 introduced iteration protocols, allowing objects to define their own iteration behavior. The iteration protocol consists of two core concepts: the iterable protocol and the iterator protocol. Any object that implements the [Symbol.iterator]()
method becomes an iterable object. This method must return an iterator object that adheres to the iterator protocol.
const myIterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 5) {
return { value: step, done: false };
}
return { done: true };
}
};
}
};
Implementing Custom Iterators
The core of a custom iterator lies in implementing the next()
method, which returns an object containing value
and done
properties. When done
is true
, it signifies the end of iteration. Below is a more complex example implementing a Fibonacci sequence iterator:
const fibonacci = {
[Symbol.iterator]() {
let prev = 0, curr = 1;
return {
next() {
[prev, curr] = [curr, prev + curr];
return { value: curr, done: false };
}
};
}
};
// Using for...of loop to iterate
for (const num of fibonacci) {
if (num > 1000) break;
console.log(num);
}
Practical Applications of Iterables
Iterable objects can be used with many ES6 features, such as the spread operator and destructuring assignment. Below demonstrates how to combine custom iterators with these features:
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
let current = this.start;
return {
next: () => {
if (current <= this.end) {
return { value: current++, done: false };
}
return { done: true };
}
};
}
}
// Using the spread operator
const numbers = [...new Range(1, 5)]; // [1, 2, 3, 4, 5]
// Destructuring assignment
const [first, second] = new Range(10, 20);
Early Termination of Iteration
Iterators can optionally implement a return()
method, which is called when iteration is terminated early (e.g., using break
or throwing an error) to perform cleanup:
const fileLines = {
[Symbol.iterator]() {
const file = openFile('data.txt');
return {
next() {
const line = file.readLine();
if (line !== null) {
return { value: line, done: false };
}
return { done: true };
},
return() {
file.close();
return { done: true };
}
};
}
};
for (const line of fileLines) {
if (line.includes('error')) break; // Triggers the return method
}
Asynchronous Iterators
ES2018 introduced asynchronous iterators, using Symbol.asyncIterator
instead of Symbol.iterator
, where the next()
method returns a Promise:
const asyncIterable = {
[Symbol.asyncIterator]() {
let i = 0;
return {
next() {
if (i < 3) {
return Promise.resolve({ value: i++, done: false });
}
return Promise.resolve({ done: true });
}
};
}
};
(async function() {
for await (const num of asyncIterable) {
console.log(num);
}
})();
Generator Functions Simplifying Iterators
Generator functions can greatly simplify iterator creation. Generator functions created with the function*
syntax automatically return an iterator:
function* alphabetGenerator() {
for (let i = 65; i <= 90; i++) {
yield String.fromCharCode(i);
}
}
const alphabet = [...alphabetGenerator()]; // ['A', 'B', ..., 'Z']
Built-in Iterable Objects
Many JavaScript built-in objects are already iterable. Understanding these can help us better utilize the iteration protocol:
// Strings are iterable
for (const char of 'hello') {
console.log(char);
}
// Map and Set are also iterable
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value);
}
Iterator Composition
Multiple iterators can be combined to create more complex data flows. Below is an example of merging two iterators:
function* mergeIterables(iterable1, iterable2) {
yield* iterable1;
yield* iterable2;
}
const merged = mergeIterables([1, 2], ['a', 'b']);
console.log([...merged]); // [1, 2, 'a', 'b']
Advanced Applications of Iterators
Iterators can be used to implement lazy evaluation, which is particularly useful when working with large datasets:
function* bigDataGenerator() {
let i = 0;
while (true) {
yield fetch(`/api/data?page=${i++}`).then(res => res.json());
}
}
const dataStream = bigDataGenerator();
// Fetch data only when needed
const firstPage = await dataStream.next().value;
const secondPage = await dataStream.next().value;
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn