阿里云主机折上折
  • 微信号
Current Site:Index > Custom iteration behavior

Custom iteration behavior

Author:Chuan Chen 阅读数:39036人阅读 分类: JavaScript

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

上一篇:内置可迭代对象

下一篇:Map数据结构

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.