Basic concepts of the iteration protocol
Core Concepts of Iteration Protocols
ECMAScript 6 introduced the iteration protocols, providing a unified mechanism for traversing data structures. The iteration protocols are not new built-in objects but a set of rules that any object can follow to implement iterable behavior. The protocols consist of two core parts: the iterable protocol and the iterator protocol.
Iterable Protocol
The iterable protocol requires an object to implement the @@iterator
method, accessible via the Symbol.iterator
key. When an object needs to be iterated (e.g., in a for...of
loop), this method is automatically called.
const iterableObject = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
}
return { done: true };
}
};
}
};
for (const item of iterableObject) {
console.log(item); // Outputs 1, 2, 3 in sequence
}
Iterator Protocol
The iterator protocol defines a standard way to produce a sequence of values. An iterator is an object that implements a next()
method, which returns an object with two properties:
value
: The current value in the iterationdone
: A boolean indicating whether the iteration is complete
function createIterator(array) {
let nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{ value: array[nextIndex++], done: false } :
{ done: true };
}
};
}
const iterator = createIterator(['a', 'b']);
console.log(iterator.next()); // { value: 'a', done: false }
console.log(iterator.next()); // { value: 'b', done: false }
console.log(iterator.next()); // { done: true }
Built-in Iterables
Many built-in objects in JavaScript are iterable by default:
- Array
- String
- Map
- Set
- arguments object
- DOM NodeList
// String iteration
for (const char of 'hello') {
console.log(char); // h, e, l, l, o
}
// Map iteration
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value); // a 1, b 2
}
Generators and Iteration Protocols
Generator functions return generator objects that are both iterable and iterators, naturally conforming to the iteration protocols:
function* generator() {
yield 1;
yield 2;
yield 3;
}
const gen = generator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
// Can also be directly iterated
for (const num of generator()) {
console.log(num); // 1, 2, 3
}
Custom Iteration Behavior
By implementing the iteration protocols, you can define iteration behavior for custom objects:
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
*[Symbol.iterator]() {
let current = this.start;
while (current <= this.end) {
yield current;
current += this.step;
}
}
}
const range = new Range(1, 5);
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
Early Termination of Iterators
Iterators can optionally implement a return()
method, which is called when iteration is terminated early (e.g., via break
or an error):
const iterable = {
[Symbol.iterator]() {
let count = 0;
return {
next() {
count++;
return { value: count, done: count > 3 };
},
return() {
console.log('Iteration terminated early');
return { done: true };
}
};
}
};
for (const item of iterable) {
console.log(item);
if (item === 2) break;
}
// Output:
// 1
// 2
// Iteration terminated early
Asynchronous Iteration Protocol
ES2018 introduced the asynchronous iteration protocol, similar to synchronous iteration but returning Promises:
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 item of asyncIterable) {
console.log(item); // 0, 1, 2
}
})();
Applications of Iteration Protocols
The iteration protocols are used by many JavaScript features:
for...of
loops- Spread operator
...
- Destructuring assignment
Array.from()
new Map()
/new Set()
Promise.all()
/Promise.race()
yield*
// Spread operator
const set = new Set([1, 2, 3]);
console.log([...set]); // [1, 2, 3]
// Destructuring assignment
const [first, second] = new Set(['a', 'b', 'c']);
console.log(first, second); // a b
// yield*
function* concatIterables(...iterables) {
for (const iterable of iterables) {
yield* iterable;
}
}
const combined = concatIterables([1, 2], 'ab', new Set([3, 4]));
console.log([...combined]); // [1, 2, 'a', 'b', 3, 4]
Iterator Utility Functions
You can create various iterator utility functions to manipulate iterables:
function* map(iterable, mapper) {
for (const item of iterable) {
yield mapper(item);
}
}
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
function* take(iterable, n) {
let count = 0;
for (const item of iterable) {
if (count++ >= n) break;
yield item;
}
}
const numbers = [1, 2, 3, 4, 5];
const result = take(filter(map(numbers, x => x * 2), x => x > 4), 2);
console.log([...result]); // [6, 8]
Performance Considerations with Iteration Protocols
Direct use of the iteration protocols can offer memory efficiency advantages by enabling lazy evaluation:
function* generateLargeArray() {
for (let i = 0; i < 1000000; i++) {
yield i;
}
}
// Does not create an array with 1 million elements all at once
for (const num of generateLargeArray()) {
if (num > 10) break;
console.log(num);
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:常用内置Symbols介绍
下一篇:可迭代对象