详解JS前端使用迭代器和生成器原理及示例

作者:小满

时间:2026-10-26

浏览:158

本文深入解析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前端迭代器生成器的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章