JavaScript迭代器和生成器

JavaScript 提供了许多迭代集合的方法,从简单的 for 循环到 forEachmapfilter。其中 whiledo...whileforfor...infor...of,可以被 label 标识,可以被 break 或者 continue 来指出程序是否该停止循环还是继续循环。

whiledo...whilefor 能够循环数组,如果要迭代对象则需要结合 Object.getOwnPropertyNames(不包括 symbol 类型)。for...in 能够循环对象(包括数组),但是它会循环对象的所有可枚举属性,包括原型链上的属性。因此,通常需要结合 hasOwnProperty 对象实例方法来过滤。

ECMAScript 2015 引入 Object.keys 返回对象 key 的数组。ECMAScript 2017 引入 Object.values 返回对象 value 的数组,Object.entries 返回对象的键值对数组。它们都是是对象自身的(不含继承的)所有可遍历(enumerable)属性的键或值,但不包括 Symbol 属性。

与之配套的是 Object.getOwnPropertySymbols,返回包含对象自身的所有 Symbol 属性键名的数组。还可以使用 Reflect.ownKeys,返回包含对象自身的(不含继承的)所有键名的数组,不管键名是 Symbol 或字符串,也不管是否可枚举。

判断对象的属性是自身的属性和继承的属性,除了 hasOwnProperty 对象实例方法,ECMAScript 2022 引入 Object.hasOwn,判断是否为自身的属性。Object.hasOwn 的一个好处是,对于不继承 Object.prototype 的对象不会报错,而 hasOwnProperty 是会报错的。

除了数组和对象,ECMAScript 2015+ 添加了新的数据结构 MapSet。为了处理所有不同数据结构,就需要一种统一的接口机制。遍历器(Iterator)就是这样一种机制,它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

Iterator 的作用有三个:

  • 为各种数据结构,提供一个统一的、简便的访问接口。
  • 使得数据结构的成员能够按某种次序排列。
  • 创造了一种新的遍历命令 for...of 循环,Iterator 接口供 for...of 消费。

迭代器

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。具体来说,迭代器是通过具有 next() 方法来实现 Iterator 协议的任何对象,该方法返回具有两个属性的对象:

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

创建迭代器对象后,可以通过重复调用 next() 来显式迭代。对迭代器进行迭代被称为消耗迭代器,因为它通常只能执行一次。产生终止值后,对 next() 的其他调用应继续返回 {done: true}

生成器函数

虽然自定义迭代器是一个有用的工具,但由于需要显式维护其内部状态,因此它们的创建需要仔细编程。生成器函数提供了一个强大的替代方案:它们允许您通过编写执行不连续的单个函数来定义迭代算法。生成器函数使用 function* 语法编写。

调用时,生成器函数最初并不执行其代码。相反,它们返回一种特殊类型的迭代器,称为生成器。当通过调用生成器的 next 方法使用某个值时,生成器函数将执行直到遇到 yield 关键字。该函数可以根据需要多次调用,并且每次都会返回一个新的生成器。每个生成器只能迭代一次。

迭代协议

迭代协议不是新的内置函数或语法,而是协议。这些协议可以由任何对象通过遵循一些约定来实现。迭代协议具体分为两个协议有两种协议:可迭代协议和迭代器协议。

可迭代协议

可迭代协议(iterable protocol)允许 JavaScript 对象定义或自定义其迭代行为,例如在 for...of 构造中循环哪些值。某些内置类型是具有默认迭代行为的内置可迭代对象,例如 ArrayMap ,而其他类型则不是。

为了可迭代,对象必须实现 @@iterator 方法,这意味着该对象(或者它原型链上的某个对象)必须具有带有 @@iterator 键的属性,该属性可通过常量 Symbol.iterator 获得:

  • [Symbol.iterator]:一个无参数的函数,其返回值为一个符合迭代器协议的对象。

每当需要迭代对象时,都会不带参数调用其 @@iterator 方法,并使用此方法返回的迭代器获得要迭代的值。值得注意的是调用此无参数函数时,它将作为对可迭代对象的方法进行调用。因此,在函数内部, this 关键字可用于访问可迭代对象的属性,以决定在迭代期间提供什么。

该函数可以是普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。在此生成器函数内部,可以使用 yield 提供每个条目。

迭代器协议

迭代器协议(iterator protocol)定义了一种生成值序列(有限或无限)的标准方法,并且在生成所有值时可能会返回一个返回值。当对象实现具有以下语义的 next() 方法时,该对象就是迭代器:

  • next():无参数或者接受一个参数的函数,并返回符合 IteratorResult 接口的对象。如果在使用迭代器内置的语言特征,得到一个非对象返回值,将会抛出 TypeError("iterator.next() returned a non-object value")

所有迭代器协议方法(next()return()throw())都应返回实现 IteratorResult 接口的对象。它必须具有以下属性:

  • done:如果迭代器能够生成序列中的下一个值,则为 false 的布尔值。如果迭代器已完成其序列,则具有值 true 。在这种情况下, value 可以选择指定迭代器的返回值。
  • value:迭代器返回的任何 JavaScript 值。当 done 为 true 时可以省略。

实际上,这两种属性都不是严格要求的。如果返回没有任一属性的对象,则它实际上相当于 { done: false, value: undefined }。如果迭代器返回 done: true 的结果,则对 next() 的任何后续调用也预计会返回 done: true,尽管这在语言级别上不强制执行。

next 方法可以接受一个值,该值将提供给方法体。任何内置的语言特征都将不会传递任何值。传递给生成器 next 方法的值将成为相应 yield 表达式的值。或者,迭代器还可以实现 return(value)throw(exception) 方法,这些方法在被调用时告诉迭代器调用者已完成迭代,并且可以执行任何必要的清理。

  • return(value):无参数或者接受一个参数的函数并返回符合 IteratorResult 接口的对象,通常 value 等于传入的 valuedone 。调用此方法告诉迭代器调用者不打算进行任何更多的 next() 调用,并且可以执行任何清理操作。
  • throw(exception):无参数或者接受一个参数的函数,并返回符合 IteratorResult 接口的对象,通常 done 等于 true。调用此方法告诉迭代器调用者检测到错误条件,并且 exception 通常是 Error 实例。

异步迭代器和异步可迭代协议

还有另一对用于异步迭代的协议,称为异步迭代器(async iterator)和异步可迭代协议(async iterable protocols)。与可迭代和迭代器协议相比,它们具有非常相似的接口,只是调用迭代器方法的每个返回值都包装在一个 Promise 中。当对象实现以下方法时,它就实现了异步可迭代协议:

  • [Symbol.asyncIterator]:返回对象的无参数函数,并且符合异步迭代器协议。

当对象实现以下方法时,它就实现了异步迭代器协议:

  • next():无参数或者接受一个参数的函数,并返回 promisePromise 会满足一个符合 IteratorResult 接口的对象,并且属性与同步迭代器的属性具有相同的语义。
  • return(value):无参数或者接受一个参数的函数,并返回 promisePromise 会满足一个符合 IteratorResult 接口的对象,并且属性与同步迭代器的属性具有相同的语义。
  • throw(exception):无参数或者接受一个参数的函数,并返回 promise``。Promise 会满足一个符合 IteratorResult 接口的对象,并且属性与同步迭代器的属性具有相同的语义。

语言和迭代协议之间的交互

StringArrayTypedArrayMapSet 以及 Intl.Segments 都是内置的可迭代对象,因为它们的每个 prototype 对象都实现了 @@iterator 方法。此外,arguments 对象和一些 DOM 集合类型,如 NodeList 也是可迭代的。ReadableStream 是唯一的内置异步可迭代对象。

生成器函数返回生成器对象,它们是可迭代的迭代器。异步生成器函数返回异步生成器对象,它们是异步可迭代迭代器。

从内置迭代器返回的迭代器实际上都继承自一个公共类 Iterator,该类实现了 [Symbol.iterator]() { return this; } 方法,使它们都是可迭代迭代器。

有许多 API 接受可迭代对象,包括:Map()WeakMap()Set()WeakSet()Promise.all()Promise.allSettled()Promise.race()Promise.any()Array.from()Object.groupBy()Map.groupBy()

相关推荐
天下无贼!43 分钟前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr43 分钟前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林1 小时前
npm发布插件超级简单版
前端·npm·node.js
我码玄黄1 小时前
THREE.js:网页上的3D世界构建者
开发语言·javascript·3d
罔闻_spider1 小时前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔1 小时前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab
爱喝水的小鼠2 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
小晗同学2 小时前
Vue 实现高级穿梭框 Transfer 封装
javascript·vue.js·elementui
盏灯2 小时前
前端开发,场景题:讲一下如何实现 ✍电子签名、🎨你画我猜?
前端
WeiShuai2 小时前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript