零基础学习JS--基础篇--迭代器和生成器

迭代器和生成器将迭代的概念直接带入核心语言,并提供了一种机制来自定义 for...of 循环的行为。

若想了解更多详情,请参考:

迭代器

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能附带一个返回值。

更具体地说,迭代器是通过使用 next() 方法实现了迭代器协议的任何一个对象,该方法返回具有两个属性的对象:

value:迭代序列的下一个值。

done:如果已经迭代到序列中的最后一个值,则它为 true。如果 valuedone 一起出现,则它就是迭代器的返回值。

一旦创建,迭代器对象可以通过重复调用 next() 显式地迭代。迭代一个迭代器被称为消耗了这个迭代器,因为它通常只能执行一次:在产生终值后,对 next() 的额外调用应该继续返回 {done:true}

Javascript 中最常见的迭代器是数组迭代器,它按顺序返回关联数组中的每个值。

虽然很容易想象所有迭代器都可以表示为数组,但事实并非如此。数组必须完整分配,而迭代器则是按需分配。因此,迭代器可以表示无限大小的序列,例如 0 和 Infinity 之间的整数范围。

下面的例子展示了具体做法。它允许你创建一个简单的范围迭代器,以定义一个从 start(闭)到 end(开),以 step 为步长的整数序列。它的最终返回值是它创建的序列的大小,由变量 iterationCount 跟踪。

javascript 复制代码
function makeRangeIterator(start = 0, end = Infinity, step = 1) {
  let nextIndex = start;
  let iterationCount = 0;

  const rangeIterator = {
    next() {
      let result;
      if (nextIndex < end) {
        result = { value: nextIndex, done: false };
        nextIndex += step;
        iterationCount++;
        return result;
      }
      return { value: iterationCount, done: true };
    },
  };
  return rangeIterator;
}

使用这个迭代器看起来像这样:

javascript 复制代码
let it = makeRangeIterator(1, 10, 2);

let result = it.next();
while (!result.done) {
  console.log(result.value); // 1 3 5 7 9
  result = it.next();
}

console.log(`已迭代序列的大小:${result.value}`); // 5

生成器函数

虽然自定义迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此创建时要格外谨慎。生成器函数 (Generator 函数)提供了一个强大的替代选择:它允许你定义一个非连续执行的函数作为迭代算法。生成器函数使用 function* 语法编写。

最初调用时,生成器函数不执行任何代码,而是返回一种称为生成器 的特殊迭代器。通过调用 next() 方法消耗该生成器时,生成器函数将执行,直至遇到 yield 关键字。

可以根据需要多次调用该函数,并且每次都返回一个新的生成器,但每个生成器只能迭代一次。

我们现在可以调整上面的例子了。此代码的行为并没有改变,但更容易编写和阅读。

javascript 复制代码
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
  let iterationCount = 0;
  for (let i = start; i < end; i += step) {
    iterationCount++;
    yield i;
  }
  return iterationCount;
}

可迭代对象

若一个对象拥有迭代行为,比如在 for...of 中会循环一些值,那么那个对象便是一个可迭代对象。一些内置类型,如 ArrayMap 拥有默认的迭代行为,而其他类型(比如 Object)则没有。

为了实现可迭代 ,一个对象必须实现 @@iterator 方法,这意味着这个对象(或其原型链中的任意一个对象)必须具有一个键值为 Symbol.iterator 的属性。

程序员应知道一个可迭代对象可以多次迭代,还是只能迭代一次。

只能迭代一次的可迭代对象(例如 Generator)通常从它们的 @@iterator 方法中返回 this,而那些可以多次迭代的方法必须在每次调用 @@iterator 时返回一个新的迭代器。

javascript 复制代码
function* makeIterator() {
  yield 1;
  yield 2;
}

const it = makeIterator();

for (const itItem of it) {
  console.log(itItem);
}

console.log(it[Symbol.iterator]() === it); // true

// 这个例子向我们展示了生成器(迭代器)是可迭代对象,
// 它有一个 @@iterator 方法返回 it(它自己),
// 因此,it 对象只能迭代*一次*。

// 如果我们将它的 @@iterator 方法改为一个返回新的迭代器/生成器对象的函数/生成器,
// 它(it)就可以迭代多次了。

it[Symbol.iterator] = function* () {
  yield 2;
  yield 1;
};
自定义的可迭代的对象:

我们可以像这样实现自己的可迭代对象:

javascript 复制代码
var myIterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
};

自定义的可迭代对象可用 for...of 循环或者展开语法进行迭代。

javascript 复制代码
for (let value of myIterable) {
  console.log(value);
}
// 1
// 2
// 3

[...myIterable]; // [1, 2, 3]
内置可迭代对象:

StringArrayTypedArrayMapSet 都是内置可迭代对象,因为它们的原型对象都拥有一个 Symbol.iterator 方法。

用于可迭代对象的语法:

一些语句和表达式专用于可迭代对象,例如 for...of 循环、展开语法yield*解构语法。

javascript 复制代码
for (let value of ["a", "b", "c"]) {
  console.log(value);
}
// "a"
// "b"
// "c"

[..."abc"]; // ["a", "b", "c"]

function* gen() {
  yield* ["a", "b", "c"];
}

gen().next(); // { value: "a", done: false }

[a, b, c] = new Set(["a", "b", "c"]);
a; // "a"

高级生成器

生成器会按需 计算它们 yield 的值,这使得它们能够高效地表示一个计算成本很高的序列,甚至是前文所示的一个无限序列。

next() 方法也接受一个参数用于修改生成器内部状态。传递给 next() 的参数值会被 yield 接收。

备注: 传给第一个next() 的值会被忽略。

下面的是斐波那契数列生成器,它使用了 next(x) 来重启序列:

javascript 复制代码
function* fibonacci() {
  let current = 0;
  let next = 1;
  while (true) {
    const reset = yield current;
    [current, next] = [next, next + current];
    if (reset) {
      current = 0;
      next = 1;
    }
  }
}

const sequence = fibonacci();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
console.log(sequence.next().value); // 3
console.log(sequence.next().value); // 5
console.log(sequence.next().value); // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2

你可以通过调用其 throw() 方法强制生成器抛出异常,并传递应该抛出的异常值。这个异常将从当前挂起的生成器的上下文中抛出,就好像当前挂起的 yield 是一个 throw value 语句。

如果该异常没有在生成器内部被捕获,则它将通过 throw() 的调用向上传播,对 next() 的后续调用将导致 done 属性为 true

生成器的 return() 方法可返回给定的值并终结这个生成器。

这一章节的内容同样是基础性少、通用性小、新接触的知识点,并且这一章节的内容不多,但是同样难于理解,里面还有很多细节大家可以点击链接进行更详细的学习。

附:以上内容均为个人在MDN网站上学习JS的笔记,若有侵权,将在第一时间删除,若有错误,将在第一时间修改。

相关推荐
前端李二牛24 分钟前
异步任务并发控制
前端·javascript
你也向往长安城吗1 小时前
推荐一个三维导航库:three-pathfinding-3d
javascript·算法
karrigan1 小时前
async/await 的优雅外衣下:Generator 的核心原理与 JavaScript 执行引擎的精细管理
javascript
wycode1 小时前
Vue2实践(3)之用component做一个动态表单(二)
前端·javascript·vue.js
wycode2 小时前
Vue2实践(2)之用component做一个动态表单(一)
前端·javascript·vue.js
第七种黄昏2 小时前
Vue3 中的 ref、模板引用和 defineExpose 详解
前端·javascript·vue.js
我是哈哈hh3 小时前
【Node.js】ECMAScript标准 以及 npm安装
开发语言·前端·javascript·node.js
张元清3 小时前
电商 Feeds 流缓存策略:Temu vs 拼多多的技术选择
前端·javascript·面试
pepedd8643 小时前
浅谈js拷贝问题-解决拷贝数据难题
前端·javascript·trae
@大迁世界3 小时前
useCallback 的陷阱:当 React Hooks 反而拖了后腿
前端·javascript·react.js·前端框架·ecmascript