JavaScript迭代器模式如何实现自定义遍历逻辑?

来源:Android社区作者:南京GEO公司头衔:草根站长
导读:本期聚焦于小伙伴创作的《JavaScript迭代器模式如何实现自定义遍历逻辑?》,敬请观看详情。直接抛出一个常见痛点:当你用for循环遍历一个树形结构时,是否被迫写下层层递归和状态标记?迭代器模式把遍历逻辑从数据结构中抽离,让调用方无需关心内部形态。在JavaScript里,只要对象实现了next方法并返回包含value与done属性的结果对象,就能成为可迭代对象;配合Symbol.iterator还能接入for...of与扩展运算符。本文通过实现一个跳过空值的数组迭代器,说明如何控制遍历节奏、惰性取值并复用遍历算法,避免把业务代码和容器实现绑死。

在JavaScript开发中,我们经常需要遍历各种数据结构,但不同的结构(如数组、树、图、自定义集合)其内部组织方式差异很大。如果每次都在业务代码里写死遍历过程,不仅冗余,而且难以维护。迭代器模式的核心思想,就是将“如何遍历”这件事从数据结构本身剥离出来,交给一个独立的迭代器对象负责。调用方只需要不断向迭代器索取下一个元素,而不必知道数据藏在哪、怎么走。

JavaScript迭代器模式如何实现自定义遍历逻辑?

什么是迭代器模式

迭代器模式是一种行为设计模式,它提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。在JavaScript中,迭代器是一个对象,必须实现next()方法。每次调用next()都会返回一个结果对象,其中包含两个属性:value表示当前元素的值,done为布尔值,指示是否已经遍历完毕。

这种设计的优势在于,遍历逻辑被封装在迭代器内部。比如一个二叉树结构,可以提供一个中序遍历的迭代器,也可以提供一个层序遍历的迭代器,而使用者的代码完全不用变。它让算法与数据结构解耦,也支持惰性计算——元素只有在被请求时才生成,这对处理海量或无限序列尤其重要。

原生JavaScript中的迭代器协议

JavaScript从ES6开始正式支持迭代器协议和可迭代协议。可迭代协议要求对象实现一个名为Symbol.iterator的方法,该方法返回一个迭代器。内置的数组、字符串、Map、Set都已经实现了该协议,因此可以直接用于for...of循环。

下面用一个简单例子展示最基本的迭代器实现。我们手动创建一个返回固定序列的迭代器,帮助理解底层机制:

// 一个返回 1,2,3 的简易迭代器
function createSimpleIterator() {
  let index = 0;
  const data = [1, 2, 3];
  return {
    next: function() {
      if (index < data.length) {
        return { value: data[index++], done: false };
      } else {
        return { value: undefined, done: true };
      }
    }
  };
}

const it = createSimpleIterator();
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: 3, done: false }
console.log(it.next()); // { value: undefined, done: true }

上面代码中,next函数通过闭包维持index状态,每次调用推进一次。当超出长度后,done变为true。这就是自定义遍历逻辑的最小原型。

自定义可迭代对象与Symbol.iterator

如果希望自己的数据结构能用在for...of中,或者能被扩展运算符展开,就需要让它成为可迭代对象。做法是在对象上添加Symbol.iterator方法,并返回符合迭代器协议的对象。

我们来实现一个“跳过空值”的数组包装器。普通数组遍历会遇到nullundefined或空字符串,很多时候业务希望直接忽略它们。把这套过滤逻辑写在迭代器里,外部遍历代码就干净了:

class SkipEmptyArray {
  constructor(arr) {
    this.arr = arr;
  }

  [Symbol.iterator]() {
    let index = 0;
    const arr = this.arr;
    return {
      next: function() {
        // 向前查找下一个非空值
        while (index < arr.length) {
          const val = arr[index++];
          if (val !== null && val !== undefined && val !== '') {
            return { value: val, done: false };
          }
        }
        return { value: undefined, done: true };
      }
    };
  }
}

const list = new SkipEmptyArray([1, null, 2, '', 3, undefined]);
for (const item of list) {
  console.log(item); // 依次输出 1 2 3
}

const spreaded = [...new SkipEmptyArray([10, null, 20])];
console.log(spreaded); // [10, 20]

这个例子展示了自定义遍历逻辑的实用性:遍历规则(跳过空值)被完全封装,调用方使用标准语法即可。如果以后规则变为“只取偶数”,只需修改迭代器内部,外部代码零改动。

用生成器简化迭代器编写

手动写next和状态变量容易出错。JavaScript的function*生成器函数能自动产生迭代器,用yield返回值,状态由引擎托管。上面的跳过空值逻辑用生成器重写会更直观:

function* skipEmptyGenerator(arr) {
  for (const item of arr) {
    if (item !== null && item !== undefined && item !== '') {
      yield item;
    }
  }
}

const gen = skipEmptyGenerator([5, null, 6, '', 7]);
console.log(gen.next().value); // 5
console.log(gen.next().value); // 6
console.log([...skipEmptyGenerator([0, false, 8])]); // [0, false, 8] 注意 0 和 false 不被跳过

生成器本质上是语法糖,但它让自定义遍历逻辑的表达成本大幅降低。你可以用循环、条件、甚至嵌套生成器来描述复杂遍历顺序,比如先序走树、按权重出队等。

在树结构中应用自定义遍历

迭代器模式在非线性结构里价值最大。假设我们有一个简单的树节点,希望外部用统一方式拿到所有节点,但不想暴露递归细节。下面给出一个深度优先的可迭代树:

class TreeNode {
  constructor(value) {
    this.value = value;
    this.children = [];
  }
  add(child) {
    this.children.push(child);
  }
  [Symbol.iterator]() {
    const stack = [this];
    return {
      next: () => {
        if (stack.length === 0) {
          return { value: undefined, done: true };
        }
        const node = stack.pop();
        // 反向压栈以保证从左到右遍历
        for (let i = node.children.length - 1; i >= 0; i--) {
          stack.push(node.children[i]);
        }
        return { value: node.value, done: false };
      }
    };
  }
}

const root = new TreeNode('root');
const a = new TreeNode('a');
const b = new TreeNode('b');
root.add(a);
root.add(b);
a.add(new TreeNode('a1'));

for (const v of root) {
  console.log(v); // root a b a1
}

这里用显式栈代替递归,迭代器每次弹出一个节点并把子节点压回。调用方完全感知不到树的存在,只拿到一条线性流。若改为广度优先,只需把栈换成队列,外部循环一行都不用改。

自定义迭代器的注意事项

实现迭代器时要注意状态隔离。如果同一个可迭代对象被多个for...of同时使用,每次调用Symbol.iterator都应返回全新的迭代器实例,否则会相互干扰。另外,迭代器一旦写完,建议保持“单向移动”语义,不要提供回退,这符合大多数语言的习惯,也降低复杂度。

性能方面,自定义迭代器通常比内置循环稍慢一点,因为多了函数调用和对象分配,但在绝大多数业务场景可以忽略。真正收益是代码清晰与复用:遍历算法只写一次,任何地方都能接for...ofArray.from、解构等语法。当数据结构演进时,只要迭代器契约不变,系统其余部分就是稳定的。

总结

JavaScript迭代器模式通过next方法与Symbol.iterator,让开发者能把任意遍历逻辑包装成标准协议。无论是过滤空值、控制步长、惰性拉取,还是遍历树与图,都可以用同一套消费方式处理。借助生成器,编写成本进一步下降。掌握它,能让你的数据结构和业务代码彻底解耦,写出更易于测试和扩展的程序。

JavaScript迭代器模式自定义遍历修改时间:2026-08-09 07:03:34

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。