详解JS前端使用迭代器和生成器原理及示例
本文深入解析JavaScript中迭代器与生成器的核心原理。通过对比for...of与for...forEach的区别,阐明可迭代对象需具备Symbol.iterator属性。详细讲解生成器函数如何生成迭代器,以及如何利用ES5手动实现next接口,帮助开发者彻底理解遍历机制并解决前端唯一ID生成等实际问题。
正文
作为前端开发者,生成器和迭代器在日常业务代码中较少直接用到,往往只在面试准备时才会重新审视。如果面试结束不久再次面对这些概念,容易产生陌生感。
这种遗忘主要源于使用频率低以及缺乏深入理解。通常对这两个概念存在以下常见误区:
1. 常将迭代器和生成器视为两个完全独立的概念。
2. 常将for、forEach、map、reduce和for of的功能混淆。
3. 常误认为数组和类数组对象都属于可迭代对象。
为了完善技术知识体系,必须厘清上述概念。
首先需明确for...of语句的具体用途。
虽然业务代码中可能不常用,但若不理解其原理,在真正需要应用的场景中可能无法正确使用甚至遗忘。
for...of的用途
开发者通常知晓for、forEach、map、reduce可用于遍历数组,而for of用于遍历可迭代对象。示例如下:
const arr = [1, 2, 3]
arr.forEach((item, index) => {
console.log(item) // 1, 2, 3
console.log(index) // 0, 1, 2
})
值得注意的是,for of同样可以遍历数组。
for (let key of arr) {
console.log(key) // 1 2 3
}
当将数组arr替换为const obj = { a: 1, b: 2, c: 3 }时,两者均无法遍历出结果。
前者无反应,后者直接报错:TypeError: obj is not iterable。这意味着类型错误:该对象不是可迭代对象。
那么,什么是可迭代对象?
可迭代对象的定义
观察以下示例:
const itemLi1 = document.getElementByTagName('li')
const itemLi2 = document.querySelectorAll('li')
for(let key of itemLi1) {
console.log(item)
}
for(let key of itemLi2) {
console.log(item)
}
可见HTMLCollection和NodeList属于可迭代对象,其他如Array、map、set、string等也是。若为类数组,是否属于可迭代对象?
const arrLike = {
0: 1,
1: 2,
2: 3,
lenght: 3
}
for (let i = 0; i < arrLike.length; i++) {
console.log(arrLike[i]) // 1, 2, 3
}
for (let of arrLike) {
console.log(key) // uncachh TypeError: obj is not iterable
}
for循环能打印结果,而for...of报错。原因在于类数组并非可迭代对象。通过对比类数组和HTMLCollection类型可知差异。

类数组结构如下:

二者显著区别在于,可迭代对象的原型链包含Symbol.iterator,这是数组成为可迭代对象的根本原因。
即当对象原型链包含Symbol.iterator时,该对象才是可迭代对象。
普通对象无序,因此不可迭代。
此处涉及Symbol类型,MDN解释其为生成全局唯一变量的符号。可迭代对象即为其应用场景之一。受此启发,前端生成唯一ID时,过去常需创建UUID函数,现在可直接使用Symbol简化操作。
由此可知,Array.prototype[Symbol.iterater]封装了特定逻辑,使得for of能打印对象元素。
换言之,Array.prototype[Symbol.iterater] = function() {}的执行生成了一个迭代器对象。
若Object.prototype也具备[Symbol.iterater]方法,for of能否遍历它?尝试如下:
Object.ptotoype[Symbol.iterator] = function value() {}
这正是生成器的作用。
生成器与迭代器的关系
ES6提供了生成器函数。既然称为生成器,其生成的对象即为迭代器。
表现形式如下:
function * generation(iterableObject) {
for(let i = 0; i < iterableObject; i++) {
yield iterableObject[i]
}
}
由*符号和yield关键字构成。
当执行const iterator = generation([1, 2, 3])时,流程如下:
iterator.next() ==> { value: 1, done: false }
iterator.next() ==> { value: 2, done: false }
iterator.next() ==> { value: 3, done: false }
iterator.next() ==> { value: undefined, done: true }
第四次调用时,value为undefined,done为true(即done为true时,value必为undefined)。因此,yield有两个作用:
- 生成一个值,并将其封装为
{ value: .., done: flase/true }形式的对象。 - 暂停执行。
显然,生成器通过next接口展示了迭代过程。
既然生成器生成迭代器,其执行结果即为迭代器,自然可被for of遍历。
for (const key of generation([1, 2, 3]) {
console.log(key) // 1, 2, 3
}
验证结果确实如此。
经典面试题:如何手动实现类似next的接口?
基于前述思路,通过标识符和判断即可用ES5实现,代码片段如下:
function generation(iterableObj) {
let nextIndex = 0
function next() {}
return {
next: () => {
return nextIndex < iterableObj.length
? { value: iterableObj[nextIndex++], done: false }
: { value: undefined, done: true }
}
}
}
当nextIndex小于数组长度时,迭代未结束。
注意:nextIndex++先执行nextIndex,再自增。
接口概念中,后台URL为网络接口;next是封装好的方法,通过它实现yield的两个作用,故next()也是前端接口。简单来说,封装好的方法即为接口。
使非迭代对象支持for...of遍历
如前所述,Symbol.iterator方法是迭代关键。可为Object挂载该方法。既然该方法使对象变为迭代器,即可复用上述ES5实现next方法的代码片段。
const obj = {
a: 1,
b: 2,
c: 3
}
Object.prototype[Symbol.iterator] = function value() {
const keys = Object.keys(Object(this))
let nextIndex = 0
function next() {
return nextIndex < keys.length
? { value: [keys[nextIndex], obj[keys[nextIndex ++]]], done: false }
: { value: undefined, done: true }
}
return {
next
}
}
for (const [key, value] in obj) {
console.log(key)
}
for循环与for...in的关系
for循环和for...in看似相似,仅共用for关键字,二者均由JS引擎底层实现。与forEach、map等基于for循环的API不同,它们构建于for循环之上。
总结
- 生成器generator执行结果即为迭代器。
- 生成器可由ES5实现,非基于底层API。
- 判断是否为迭代器的关键在于
Symbol.iterator方法。
以上为详解JS前端使用迭代器和生成器原理及示例的详细内容,更多关于JS前端迭代器生成器的资料请关注本站其它相关文章!
JS空字符串、null和undefined转数字的结果是什么
2026-11-05 14:49:16
CodePen、JSFiddle与StackBlitz:主流JS在线编辑器对比
2026-11-05 13:38:29
JS怎么将时间戳转换为“刚刚”、“几分钟前”等相对时间
2026-11-05 12:55:14
JavaScript将儒略日逆向还原为公历日期时间的实现方法
2026-11-04 23:51:05
能在我的世界手机版直接修改JS代码吗
2026-11-04 19:02:53
