深入理解ES6迭代器与生成器:从原理到实战

作者:拾光

时间:2026-10-22

浏览:266

本文深入解析ES6中的迭代器与生成器机制。通过对比传统for循环,阐述迭代器消除复杂性的优势。详解生成器函数的创建方式、Symbol.iterator协议以及内置集合的迭代器行为,并结合for-of循环与展开运算符展示实际应用场景,帮助开发者高效处理数据遍历。

文章目录

本文深入解析ES6标准中的迭代器(Iterator)与生成器(Generator)机制,旨在帮助开发者掌握相关核心概念。

传统循环语句的局限性

传统循环语句的局限性


var colors = ["red", "green", "blue"];
for(var i=0; i<colors.length; i++){
  console.log(colors[i]);
}

在ES6标准发布之前,开发者通常依赖标准的for循环,通过索引变量来遍历数组。当涉及多层嵌套循环时,需要同时维护多个索引变量,这不仅显著增加了代码复杂度,还容易因循环变量误用而引发难以排查的Bug。

迭代器的引入正是为了解决上述复杂性,旨在简化遍历逻辑并减少循环过程中的错误。

迭代器的定义与原理

迭代器的定义与原理

为了直观理解,我们可以先尝试使用ES5语法手动模拟创建一个迭代器对象。


function createIterator(items) {
  var i = 0;
  
  return { // 返回一个迭代器对象
    next: function() { // 迭代器对象一定有个next()方法
      var done = (i >= items.length);
      var value = !done ? items[i++] : undefined;
      
      return { // next()方法返回结果对象
        value: value,
        done: done
      };
    }
  };
}

var 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: undefiend, done: true}"
// 之后所有的调用都会返回相同内容
console.log(iterator.next()); // "{ value: undefiend, done: true}"

上述代码通过调用createIterator()函数,返回一个包含next()方法的对象。每次调用next()时,该对象会返回一个格式为{ value: 1, done: false }的结果对象,其中value表示当前值,done表示遍历是否结束。

基于此,我们可以将迭代器定义为:一个拥有next()方法的特殊对象,每次调用next()方法都会返回一个包含value和done属性的结果对象。

利用这个迭代器对象,我们可以对前述的标准for循环进行重构(此处暂不考虑ES6新增的for-of循环特性)。


var colors = ["red", "green", "blue"];
var iterator = createIterator(colors);
while(!iterator.next().done){
  console.log(iterator.next().value);
}

读者可能会质疑:仅仅为了消除循环变量,却引入了复杂的对象结构,代码量反而增加,这是否得不偿失?

实际上并非如此。createIterator()函数只需编写一次,即可被反复复用。此外,ES6引入了生成器(Generator)对象,使得创建迭代器的过程变得更加简洁直观。

生成器的基本概念

生成器的基本概念

生成器是一种专门用于返回迭代器的函数。在语法上,它通过在function关键字后添加星号(*)来标识,并在函数体内部使用新的yield关键字。


function *createIterator(items) {
  for(let i=0; i<items.length; i++) {
    yield items[i];
  }
}

let iterator = createIterator([1, 2, 3]);

// 既然生成器返回的是迭代器,自然就可以调用迭代器的next()方法
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: undefiend, done: true}"
// 之后所有的调用都会返回相同内容
console.log(iterator.next()); // "{ value: undefiend, done: true}"

如上所示,利用ES6的生成器大大简化了迭代器的创建流程。当我们将items数组传入生成器函数createIterator()后,函数内部的for循环会不断从数组中提取元素并通过yield语句放入迭代器。每当遇到yield语句,循环执行便会暂停;每次调用迭代器的next()方法时,循环继续运行并再次暂停在下一个yield语句处。

生成器的多种创建方式

生成器的多种创建方式

生成器本质上是一种函数,可以通过多种方式定义。


function *createIterator(items) { ... }

可以使用函数表达式的方式书写生成器。


let createIterator = function *(item) { ... }

也可以将生成器添加到对象中,例如采用ES5风格的对象字面量写法。


let o = {
  createIterator: function *(items) { ... }
};

let iterator = o.createIterator([1, 2, 3]);

在ES6中,还可以使用对象方法的简写语法来定义生成器。


let o = {
  *createIterator(items) { ... }
};

let iterator = o.createIterator([1, 2, 3]);

可迭代对象的概念

可迭代对象的概念

在ES6标准中,所有的集合对象(包括数组、Set集合和Map集合)以及字符串都被定义为可迭代对象(Iterable)。这些对象都绑定了默认的迭代器。

随之而来的是ES6新增的循环特性for-of。


var colors = ["red", "green", "blue"];
for(let color of colors){
  console.log(color);
}

for-of循环可以直接作用于可迭代对象,其底层正是利用了可迭代对象上绑定的默认迭代器。具体执行过程为:for-of循环每执行一次,都会调用可迭代对象的next()方法,并将迭代器返回结果对象中的value属性值赋给循环变量。该过程将持续进行,直到返回对象的done属性值为true时停止。

如果仅需迭代数组或集合中的元素值,使用for-of循环替代传统的for循环是一个更为简洁的选择。

访问默认迭代器

访问默认迭代器

每个可迭代对象都拥有一个Symbol.iterator方法。在执行for-of循环时,JavaScript引擎会在后台自动调用如colors数组的Symbol.iterator方法来获取默认迭代器。

我们可以手动获取该默认迭代器以观察其工作原理。


let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();

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}"

在上述代码中,通过Symbol.iterator属性获取了数组values的默认迭代器,并利用它遍历数组元素。JavaScript引擎在执行for-of循环语句时,采用的处理机制与此类似。

可以使用Symbol.iterator属性来检测一个对象是否为可迭代对象。


function isIterator(object) {
  return typeof object[Symbol.iterator] === "function";
}

console.log(isIterable([1, 2, 3])); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new Map())); // true
console.log(isIterable("Hello")); // true

创建自定义可迭代对象

创建自定义可迭代对象

在创建自定义对象时,若给Symbol.iterator属性赋值一个生成器,即可将该对象转换为可迭代对象。


let collection = {
  items: [],
  *[Symbol.iterator]() { // 将生成器赋值给对象的Symbol.iterator属性来创建默认的迭代器
    for(let item of this.items) {
      yield item;
    }
  }
};

collection.items.push(1);
collection.items.push(2);
collection.items.push(3);

for(let x of collection) {
  console.log(x);
}

内建迭代器的类型

内建迭代器的类型

ES6中的集合对象,包括数组、Set集合和Map集合,都内建了三种特定的迭代器方法。

  • entries():返回一个迭代器,其值为多个键值对。对于数组,第一个元素是索引位置;对于Set集合,第一个元素与第二个元素相同,均为值本身。
  • values():返回一个迭代器,其值为集合中包含的值。
  • keys():返回一个迭代器,其值为集合中的所有键名。对于数组,返回的是索引;对于Set集合,返回的是值(因为Set的值同时被用作键和值)。

不同集合的默认迭代器行为

不同集合的默认迭代器行为

每种集合类型都关联一个默认迭代器。在for-of循环中,若未显式指定迭代器方法,则默认使用关联的迭代器。根据常规使用习惯,数组和Set集合的默认迭代器为values(),而Map集合的默认迭代器为entries()。

以下示例展示了不同集合的默认迭代器输出结果。


let colors = [ "red", "green", "blue"];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();

data.set("title", "Understanding ECMAScript 6");
data.set("format", "print");

// 与调用colors.values()方法相同
for(let value of colors) {
  console.log(value);
}

// 与调用tracking.values()方法相同
for(let num of tracking) {
  console.log(num);
}

// 与调用data.entries()方法相同
for(let entry of data) {
  console.log(entry);
}

该段代码执行后会输出以下内容。

"red" "green" "blue" 1234 5678 9012 ["title", "Understanding ECMAScript 6"] ["format", "print"]

结合解构特性,for-of循环在操作数据时更加便捷高效。


for(let [key, value] of data) {
  console.log(key + "=" + value);
}

展开运算符与可迭代对象

展开运算符与可迭代对象


let set = new Set([1, 2, 3, 4, 5]),
  array = [...set];
  
console.log(array); // [1,2,3,4,5]

展开运算符(Spread Operator)可以操作所有可迭代对象,并依据默认迭代器选取引用的值,从迭代器中读取所有值后,按返回顺序依次插入到新数组中。因此,若需将可迭代对象转换为数组,使用展开运算符是最简便的方法。

以上就是本文关于ES6迭代器与生成器的全部内容,希望对读者的学习有所帮助,也欢迎大家继续支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章