ES6 迭代器与可迭代对象详解:原理、生成器与实现
本文深入解析 ES6 中的迭代器与可迭代对象机制。通过生成器函数简化迭代器创建,详解 Symbol.iterator 协议及 for-of 循环原理,并介绍展开运算符在可迭代对象中的应用。内容涵盖内置集合的默认迭代器实现及自定义可迭代对象的开发方法。
ES6 迭代器与可迭代对象概述
ES6 中的数组新方法、集合类型、for-of 循环、展开运算符(...)以及异步编程等特性,均建立在迭代器(Iterator)的基础之上。本文将深入解析 ES6 的迭代器与生成器机制,并进一步探讨可迭代对象的内部工作原理及其具体应用方式。
迭代器的基本原理
一、迭代器的基本原理
在传统的编程语言中,处理数组或集合数据时,通常需要编写循环语句并手动初始化变量来记录当前的迭代位置。而通过程序化方式使用迭代器,可以显著简化这类数据遍历的操作流程。
那么,如何设计一个迭代器呢?
迭代器本质上是一个对象,该对象包含一个 next() 方法,该方法返回一个结果对象。这个结果对象包含两个属性:value(表示下一个返回值)和 done(布尔值,表示迭代是否完成)。以下代码模拟创建了一个简单的迭代器:
function createIterator(iterms) {
let i = 0
return {
next() {
let done = (i >= iterms.length)
let value = !done ? iterms[i++] : undefined
return {
done,
value
}
}
}
}
let arrayIterator = createIterator([1, 2, 3])
console.log(arrayIterator.next()) // { done: false, value: 1 }
console.log(arrayIterator.next()) // { done: false, value: 2 }
console.log(arrayIterator.next()) // { done: false, value: 3 }
console.log(arrayIterator.next()) // { done: true, value: undefined }
如果对上述语法感到困惑,可以参考相关文档:【ES6】对象的新功能与解构赋值。
每次调用迭代器的 next() 方法,都会返回数据集中的下一个元素,直到数据集被完全遍历完毕。
ES6 中迭代器的编写规则与此类似,但引入了生成器(Generator)对象,使得创建迭代器变得更加简单。
创建迭代器与生成器
二、创建迭代器
ES6 封装了一个生成器用于创建迭代器。生成器本质上是返回迭代器的函数,通过在 function 关键字后添加星号(*)来标识,并使用新的内部专用关键字 yield 来指定迭代器 next() 方法的返回值。
如何使用 ES6 生成器创建一个迭代器?以下是一个简单的示例:
function *createIterator() {
yield 123;
yield 'someValue'
}
let someIterator = createIterator()
console.log(someIterator.next()) // { value: 123, done: false }
console.log(someIterator.next()) // { value: 'someValue', done: false }
console.log(someIterator.next()) // { value: undefined, done: true }
使用 yield 关键字可以返回任意值或表达式,从而可以批量为迭代器添加元素:
// let createIterator = function *(items) { // 生成器函数表达式
function *createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i]
}
}
let someIterator = createIterator([123, 'someValue'])
console.log(someIterator.next()) // { value: 123, done: false }
console.log(someIterator.next()) // { value: 'someValue', done: false }
console.log(someIterator.next()) // { value: undefined, done: true }
由于生成器本身也是一种函数,因此可以将其添加到对象中,使用方式如下:
let obj = {
// createIterator: function *(items) { // ES5
*createIterator(items) { // ES6
for (let i = 0; i < items.length; i++) {
yield items[i]
}
}
}
let someIterator = obj.createIterator([123, 'someValue'])
生成器函数的一个显著特点是,当执行完一条 yield 语句后,函数会自动暂停执行。只有再次调用迭代器的 next() 方法时,才会继续执行下一条 yield 语句。
这种自动中止函数执行的能力衍生出了许多高级用法。
可迭代对象与 Symbol.iterator
三、可迭代对象
在 ES6 中,常见的集合对象(如数组、Set 和 Map 集合)以及字符串都是可迭代对象。这些对象都拥有默认的迭代器和 Symbol.iterator 属性。
通过生成器创建的迭代器本身也是可迭代对象,因为生成器默认会为 Symbol.iterator 属性赋值。
3.1 Symbol.iterator 协议
可迭代对象具有 Symbol.iterator 属性,即任何拥有 Symbol.iterator 属性的对象都具备默认迭代器。
我们可以使用 Symbol.iterator 来访问对象的默认迭代器,例如对于一个数组:
let list = [11, 22, 33]
let iterator = list[Symbol.iterator]()
console.log(iterator.next()) // { value: 11, done: false }
Symbol.iterator 获取了数组这个可迭代对象的默认迭代器,并通过操作它来遍历数组中的元素。
反之,我们可以使用 Symbol.iterator 来检测一个对象是否为可迭代对象:
function isIterator(obj) {
return typeof obj[Symbol.iterator] === 'function'
}
console.log(isIterator([11, 22, 33])) // true
console.log(isIterator('sometring')) // true
console.log(isIterator(new Map())) // true
console.log(isIterator(new Set())) // true
console.log(isIterator(new WeakMap())) // false
console.log(isIterator(new WeakSet())) // false
显然,数组、Set/Map 集合和字符串都是可迭代对象,而 WeakSet/WeakMap 集合(弱引用集合)则是不可迭代的。
创建可迭代对象
3.2 创建可迭代对象
默认情况下,自定义的对象都是不可迭代的。
前文已述,通过生成器创建的迭代器也是一种可迭代对象,生成器默认会为 Symbol.iterator 属性赋值。
那么,如何将自定义对象变为可迭代对象呢?可以通过给 Symbol.iterator 属性添加一个生成器来实现:
let collection = {
items: [11,22,33],
*[Symbol.iterator]() {
for (let item of this.items){
yield item
}
}
}
console.log(isIterator(collection)) // true
for (let item of collection){
console.log(item) // 11 22 33
}
数组 items 是可迭代对象,而 collection 对象通过给 Symbol.iterator 属性赋值,也变成了可迭代对象。
for-of 循环原理
3.3 for-of 循环
注意到前面的示例使用了 for-of 代替了传统的索引循环,for-of 是 ES6 为可迭代对象新加入的特性。
让我们思考一下 for-of 循环的实现原理。
对于使用 for-of 的可迭代对象,for-of 每执行一次就会调用该可迭代对象的 next() 方法,并将返回结果存储在一个变量中,持续执行直到可迭代对象的 done 属性值为 false。
// 迭代一个字符串
let str = 'somestring'
for (let item of str){
console.log(item) // s o m e s t r i n g
}
本质上来说,for-of 调用 str 字符串的 Symbol.iterator 属性方法获取迭代器(这个过程由 JS 引擎自动完成),然后多次调用 next() 方法,将对象的 value 值存储在 item 变量中。
将 for-of 用于不可迭代对象、null 或 undefined 会报错!
展开运算符的应用
3.4 展开运算符(...)
ES6 语法糖中的展开运算符(...)也是服务于可迭代对象的,即它只能“展开”数组、集合、字符串以及自定义的可迭代对象。
以下示例展示了不同可迭代对象使用展开运算符计算的结果:
let str = 'somestring'
console.log(...str) // s o m e s t r i n g
let set = new Set([1, 2, 2, 5, 8, 8, 8, 9])
console.log(set) // Set { 1, 2, 5, 8, 9 }
console.log(...set) // 1 2 5 8 9
let map = new Map([['name', 'jenny'], ['id', 123]])
console.log(map) // Map { 'name' => 'jenny', 'id' => 123 }
console.log(...map) // [ 'name', 'jenny' ] [ 'id', 123 ]
let num1 = [1, 2, 3], num2 = [7, 8, 9]
console.log([...num1, ...num2]) // [ 1, 2, 3, 7, 8, 9 ]
let udf
console.log(...udf) // TypeError: undefined is not iterable
由以上代码可以看出,展开运算符(...)可以便捷地将可迭代对象转换为数组。与 for-of 一样,展开运算符(...)用于不可迭代对象、null 或 undefined 时会报错!
内置对象的默认迭代器
四. 默认迭代器
ES6 为许多内置对象提供了默认的迭代器,只有当内建的迭代器不能满足需求时,才需要自己创建迭代器。
ES6 的三个集合对象:Set、Map、Array 都有默认的迭代器。常用的如 values() 方法、entries() 方法都返回一个迭代器,其值区别如下:
- entries():返回多个键值对
- values():返回集合的值
- keys():返回集合的键
调用以上方法都可以得到集合的迭代器,并使用 for-of 循环,示例如下:
/******** Map ***********/
let map = new Map([['name', 'jenny'], ['id', 123]])
for(let item of map.entries()){
console.log(item) // [ 'name', 'jenny' ] [ 'id', 123 ]
}
for(let item of map.keys()){
console.log(item) // name id
}
for (let item of map.values()) {
console.log(item) // jenny 123
}
/******** Set ***********/
let set = new Set([1, 4, 4, 5, 5, 5, 6, 6,])
for(let item of set.entries()){
console.log(item) // [ 1, 1 ] [ 4, 4 ] [ 5, 5 ] [ 6, 6 ]
}
/********* Array **********/
let array = [11, 22, 33]
for(let item of array.entries()){
console.log(item) // [ 0, 11 ] [ 1, 22 ] [ 2, 33 ]
}
此外,String 和 NodeList 类型都有默认的迭代器,虽然没有提供其他方法,但可以使用 for-of 循环进行遍历。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
