JS【详解】迭代器 Iterator(含可迭代对象、同步迭代器、异步迭代器等)

什么是迭代器?

JS 迭代器是一种遍历访问数据结构中所有成员的机制,本质是一个指针对象。

为什么要有迭代器?

  • 为各种不同的数据结构提供统一的访问机制。
  • 自定义数据结构的遍历:当你创建了一个自定义的数据结构时,可以实现迭代器来方便地遍历其中的元素。
  • 惰性计算:迭代器可以实现惰性计算,即只有在需要时才计算下一个值,这样可以节省内存和计算资源。
  • 异步迭代器,可以用于处理异步数据流。

迭代器的遍历过程

  • 创建一个指针对象,指向数据结构的起始位置。

  • 第一次调用指针对象的 next 方法,指针指向数据结构的第一个成员。

  • 第二次调用指针对象的 next 方法,指针指向数据结构的第二个成员。

  • 依此类推,不断调用指针对象的 next 方法……

  • 当指针指向数据结构的结束位置,遍历结束

可迭代对象

实现了迭代器的对象(拥有返回一个具备 next 方法的 Symbol.iterator 方法),被称为可迭代对象(如字符串、数组、Set、Map 、Object 等),可通过 for…of 语句遍历。

Symbol.iterator 是一个 Symbol 类型的值,它是 JavaScript 内置的一个特殊 Symbol,用来表示对象的迭代器方法。由于它是 Symbol 类型而非普通字符串,所以不能直接当作静态属性名来使用,而需要通过计算属性名的方式来定义或访问(即需要用 [] 包裹使用)。

可迭代对象调用其 Symbol.iterator 方法会得到一个用于遍历该对象元素的迭代器。

const arr = [1, 2];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next());  // { value: 1, done: false }
console.log(iterator.next());  // { value: 2, done: false }
console.log(iterator.next());  // { value: undefined, done: true }

内置可迭代对象

JS 内置实现了迭代器的对象(具有 Symbol.iterator 属性)有:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

自定义可迭代对象

// 创建一个可迭代对象
const myIteratorObject = {
    
    
    data: [1, 2, 3, 4, 5],
    [Symbol.iterator]() {
    
    
        let index = 0;
        return {
    
    
            next: () => {
    
    
                if (index < this.data.length) {
    
    
                    return {
    
     value: this.data[index++], done: false };
                } else {
    
    
                    return {
    
     done: true };
                }
            }
        };
    }
};

// 使用 for...of 循环遍历自定义的可迭代对象
for (const value of myIteratorObject) {
    
    
    console.log(value);
}

触发迭代器的场景

  • for…of 循环

  • 对数组和 Set 结构进行解构赋值

  • 扩展运算符(…)

  • yield*
    可迭代对象在 yield* 后时,会触发迭代器

    let generator = function* () {
          
          
      yield 1;
      yield* [2,3,4];
      yield 5;
    };
    
    var iterator = generator();
    
    iterator.next() // { value: 1, done: false }
    iterator.next() // { value: 2, done: false }
    iterator.next() // { value: 3, done: false }
    iterator.next() // { value: 4, done: false }
    iterator.next() // { value: 5, done: false }
    iterator.next() // { value: undefined, done: true }
    
  • Array.from()

  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))

  • Promise.all()

  • Promise.race()

可迭代对象转数组

只要某个数据结构实现了迭代器,就可以对它使用扩展运算符,将其转为数组。

let arr = [...iterable];

return()方法

可迭代对象除了具有next()方法,还可以具有return()方法,但可以选择性实现。

如果for…of循环提前退出(通常是因为出错,或者有break语句),就会调用return()方法。

如果一个对象在完成遍历前,需要清理或释放资源,就可以通过return()方法。

return()方法必须返回一个对象

function readLinesSync(file) {
    
    
  return {
    
    
    [Symbol.iterator]() {
    
    
      return {
    
    
        next() {
    
    
          return {
    
     done: false };
        },
        return() {
    
    
          file.close();
          return {
    
     done: true };
        }
      };
    },
  };
}

下面的两种情况,都会触发执行return()方法。

// 情况一
for (let line of readLinesSync(fileName)) {
    
    
  console.log(line);
  break;
}

// 情况二
for (let line of readLinesSync(fileName)) {
    
    
  console.log(line);
  throw new Error();
}

同步迭代器

迭代器有 next 方法,返回一个包含 value 和 done 两个属性的对象。

  • value 属性:当前迭代位置的值,可为任意类型( TS 中为 any) 。(即当前指针指向的数据结构的成员),当值为 undefined 时可省略。
  • done 属性:是否已迭代结束,布尔值,true 表明迭代结束,false 则意味着还有成员可供迭代,当值为 false 时可省略。
// 自定义迭代器
const myIterator = {
    
    
    // 需要遍历的成员
    data:[1,2,3],
    // 因第一个元素下标为 0 ,起始位置从 -1 开始
    index: -1,
    // 迭代器的核心方法 next 
    next() {
    
    
        this.index++;
        // next 方法返回一个包含 value 和 done 两个属性的对象
        return {
    
    
        value: this.data[this.index], 
        done:  this.index === this.data.length };
    },
    // 为了后续可用 for of 语句遍历,详见下文中可迭代对象的解析
    [Symbol.iterator]() {
    
    
        return this;
    },
};

for (const item of myIterator) {
    
    
    console.log(item);  //1 2 3
}

异步迭代器

异步迭代器和同步迭代器相似,主要用于处理异步数据。

其 next 方法返回一个 Promise,该 Promise 会解析为一个包含 value 和 done 属性的对象。

// 定义异步迭代器
const asyncNumberIterator = {
    
    
    current: 0,
    max: 3,
    async next() {
    
    
        // 模拟异步操作
        await new Promise(resolve => setTimeout(resolve, 1000));
        if (this.current <= this.max) {
    
    
            const value = this.current++;
            return {
    
     value, done: false };
        }
        return {
    
     done: true };
    },
    [Symbol.asyncIterator]() {
    
    
        return this;
    }
};

// 使用异步迭代器,需用 for await of
(async () => {
    
    
    for await (const num of asyncNumberIterator) {
    
    
        console.log(num);
    }
})();

TS 给迭代器标注类型

// 迭代器接口
interface Iterable {
    
    
  [Symbol.iterator]() : Iterator,
}

// 迭代器指针
interface Iterator {
    
    
  next(value?: any) : IterationResult,
}

// next 的返回值
interface IterationResult {
    
    
  value: any,
  done: boolean,
}

使用生成器实现迭代器

用 yield 命令给出每一步的返回值即可。

let obj = {
    
    
  * [Symbol.iterator]() {
    
    
    yield 'hello';
    yield 'world';
  }
};

for (let x of obj) {
    
    
  console.log(x);
}
// "hello"
// "world"

【实战】迭代器实现“链表”结构

function Obj(value) {
    
    
  this.value = value;
  this.next = null;
}

Obj.prototype[Symbol.iterator] = function() {
    
    
  var iterator = {
    
     next: next };

  var current = this;

  function next() {
    
    
    if (current) {
    
    
      var value = current.value;
      current = current.next;
      return {
    
     done: false, value: value };
    }
    return {
    
     done: true };
  }
  return iterator;
}

var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);

one.next = two;
two.next = three;

for (var i of one){
    
    
  console.log(i); // 1, 2, 3
}

【实战】类似数组的对象实现迭代器

存在数值键名和length属性的对象为类似数组的对象。

将其 Symbol.iterator方法直接引用数组的 Iterator 接口可快捷实现迭代器

let iterable = {
    
    
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
    
    
  console.log(item); // 'a', 'b', 'c'
}