Iterable object
The Iterable Protocol
ECMAScript 6 introduced the Iterable Protocol, which allows objects to define or customize their iteration behavior. For an object to be iterable, it must implement the @@iterator
method, meaning the object or its prototype chain must have a property with the key Symbol.iterator
.
const iterableObject = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 5) {
return { value: step, done: false };
}
return { value: undefined, done: true };
}
};
}
};
Built-in Iterable Objects
Many built-in objects in ES6 are iterable by default, including:
- Array
- String
- Map
- Set
- TypedArray
- The
arguments
object - DOM NodeList
// Arrays are iterable
const arr = [1, 2, 3];
for (const item of arr) {
console.log(item);
}
// Strings are also iterable
const str = 'hello';
for (const char of str) {
console.log(char);
}
The Iterator Protocol
The Iterator Protocol defines a standard way to produce a sequence of values. For an object to be an iterator, it must implement a next()
method, which returns an object with two properties:
value
: The value returned by the iteratordone
: A boolean indicating whether the iteration is complete
function createIterator(array) {
let index = 0;
return {
next() {
return index < array.length
? { value: array[index++], done: false }
: { value: undefined, done: true };
}
};
}
const iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
Custom Iterable Objects
We can create custom iterable objects by implementing the Symbol.iterator
method:
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
const step = this.step;
return {
next() {
if (step > 0 ? current <= end : current >= end) {
const value = current;
current += step;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
const range = new Range(1, 5);
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
Use Cases for Iterable Objects
Iterable objects have various applications in ES6:
for...of
loops: Specifically designed to iterate over iterable objects- Spread operator: Can expand an iterable object into individual elements
- Destructuring assignment: Can extract values from an iterable object
Array.from()
: Converts an iterable object into an arrayMap
,Set
,WeakMap
,WeakSet
constructors: Accept iterable objects as arguments
// for...of
const set = new Set([1, 2, 3]);
for (const item of set) {
console.log(item);
}
// Spread operator
const arr = [...'hello']; // ['h', 'e', 'l', 'l', 'o']
// Destructuring assignment
const [first, second] = new Set([1, 2, 3]);
console.log(first, second); // 1 2
// Array.from
const map = new Map([[1, 'one'], [2, 'two']]);
const arrayFromMap = Array.from(map); // [[1, 'one'], [2, 'two']]
Generators and Iterable Objects
Generator functions return generator objects that are both iterators and iterable, making it more concise to create iterable objects:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
const sequence = generateSequence(1, 5);
for (const num of sequence) {
console.log(num); // 1, 2, 3, 4, 5
}
// Generator objects are both iterators and iterable
const anotherSequence = generateSequence(1, 3);
console.log(anotherSequence[Symbol.iterator]() === anotherSequence); // true
Asynchronous Iterable Objects
ES2018 introduced asynchronous iterators and asynchronous iterable objects for handling asynchronous data streams:
async function* asyncGenerateSequence(start, end) {
for (let i = start; i <= end; i++) {
// Simulate an asynchronous operation
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
(async () => {
const asyncSequence = asyncGenerateSequence(1, 3);
for await (const num of asyncSequence) {
console.log(num); // 1 (after 100ms), 2 (after 200ms), 3 (after 300ms)
}
})();
Combining Iterable Objects
Iterable objects can be combined to create more complex data processing pipelines:
function* take(iterable, count) {
let taken = 0;
for (const item of iterable) {
if (taken >= count) return;
yield item;
taken++;
}
}
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
function* map(iterable, mapper) {
for (const item of iterable) {
yield mapper(item);
}
}
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = take(
map(
filter(numbers, n => n % 2 === 0),
n => n * 2
),
3
);
console.log([...result]); // [4, 8, 12]
Performance Considerations for Iterable Objects
When working with large datasets, iterable objects are more memory-efficient than arrays because they generate values on demand rather than all at once:
function* generateLargeDataset() {
for (let i = 0; i < 1e6; i++) {
yield i;
}
}
// Memory-efficient because values are generated on demand
for (const num of generateLargeDataset()) {
if (num > 10) break;
console.log(num);
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn