首页
/ freeCodeCamp 基础数据结构教程:用 for...in 遍历 JavaScript 对象的所有键

freeCodeCamp 基础数据结构教程:用 for...in 遍历 JavaScript 对象的所有键

2026-09-06 18:47:52作者:尤峻淳Whitney

本篇技术指南以 freeCodeCamp 开源仓库中的一道经典课程关卡 Iterate Through the Keys of an Object with a for...in Statement 为骨架,系统讲解 for...in 循环遍历对象键的原理、与数组索引遍历的差异,并结合关卡内真实出现的 countOnline 练习、测试断言(hints)与官方解(solution),帮助你彻底掌握"遍历对象键 + 方括号动态取值 + 计数器"这一组合套路。读完你将能独立实现此类"按对象键聚合统计"的函数,并理解该知识点在 freeCodeCamp Basic Data Structures 整个课程模块中的承接位置。

一、为什么要遍历对象的键

在 freeCodeCamp 的 Basic Data Structures(基础数据结构)模块中,前几个关卡已经带你完成了对象的增删改查:添加键值对(Add Key-Value Pairs to JavaScript Objects)、修改嵌套对象、用方括号动态访问属性名(Access Property Names with Bracket Notation)、用 delete 删除属性,以及用 hasOwnProperty() / in 检查属性是否存在(Check if an Object has a Property)。

然而以上操作都是针对"单个已知键"展开的。真实场景里你常常不知道对象里到底有哪些键——例如一个聊天系统保存了在线用户集合,每个用户是一个键,对应的值是一个 { online: true/false } 对象。此时你需要一种遍历全部键的手段,这就是本关主角 for...in 的用武之地。

二、for...in 基本语法与运行过程

关卡 description 给出的语法结构如下:

const refrigerator = {
  'milk': 1,
  'eggs': 12,
};

for (const food in refrigerator) {
  console.log(food, refrigerator[food]);
}

运行这段代码会在控制台逐行打印:

milk 1
eggs 12

它的执行过程分三步理解:

  1. 循环头声明迭代变量for (const food in refrigerator) 在每次迭代开始时,把 food 变量设置为对象当前枚举到的那个键(字符串 'milk''eggs')。
  2. 循环体读取键名:直接使用 food 即可拿到当前键的名字。
  3. 循环体读取键值:因为 food 是运行时才知道的字符串变量,必须使用方括号语法 refrigerator[food] 才能取得对应的值——这正是上一关卡 Access Property Names with Bracket Notation 强调过的:当属性名是"动态计算出来的"而非字面量时,点号语法不可用,方括号才有效。

换句话说,for...in 的本质是"为对象里的每一个键执行一次循环体",它把"枚举键名"和"按键取值"两条信息都暴露给了你,从而支持对每个键值对做统一处理。

关于对象键顺序的重要说明

关卡中专门给出了一条 NOTE(注意事项),这也是初学者最容易踩坑的地方:

Objects do not maintain an ordering to stored keys like arrays do; thus a key's position on an object, or the relative order in which it appears, is irrelevant when referencing or accessing that key.

翻译过来就是:数组保证元素有确定下标顺序,而对象并不对键维护这种顺序保证。所以你不能依赖 for...in 的迭代顺序去完成依赖"先后次序"的逻辑(例如按顺序拼接字符串或做位置相关计算)。设计算法时,应当把每次循环体迭代视为"处理一个键值对",并保证逻辑与迭代顺序无关。这也是为什么后续关卡 Generate an Array of All Object Keys with Object.keys() 中会特别重申"返回的键数组同样没有特定顺序"。

三、实战关卡:countOnline 在线人数统计

理解了语法后,本关抛出一个贴近真实业务的小任务(instructions):

We've defined a function countOnline which accepts one argument, allUsers. Use a for...in statement inside this function to loop through the allUsers object and return the number of users whose online property is set to true.

要求实现一个接收 allUsers 参数的 countOnline 函数,用 for...in 遍历其中每个用户,统计出 onlinetrue 的用户个数并返回。题目给出的示例输入是一个"用户 → 状态对象"的映射结构:

{
  Alan: {
    online: false
  },
  Jeff: {
    online: true
  },
  Sarah: {
    online: false
  }
}

它的特点是"两层对象":外层对象的键是用户名,内层每个值又是一个带 online 布尔属性的对象。因此遍历时,allUsers[user].online 才能访问到在线状态。题目同时保证每个用户都会有 online 属性且其值为 truefalse,所以你不需要做额外的存在性检查——若需要判断属性是否存在,可回看上一关的 hasOwnProperty / in 写法(参考 Check if an Object has a Property 及 Use the delete Keyword to Remove Object Properties)。

关卡初始代码(seed)

以下是关卡预置的起始代码:

const users = {
  Alan: {
    online: false
  },
  Jeff: {
    online: true
  },
  Sarah: {
    online: false
  }
};

function countOnline(allUsers) {
  // Only change code below this line

  // Only change code above this line
}

console.log(countOnline(users));

学习者只需在 // Only change code below this line// Only change code above this line 之间补全函数体。这种"预置测试数据 + 输出验证 + 限定改动区间"的关卡格式,在仓库的课程文件中被称为 seed-contents,对应数据结构由 curriculum/schema/challenge-schema.js 中的 seedsolutions 等字段定义。

四、官方参考解法逐行拆解

关卡 # --solutions-- 段给出的参考实现非常精简且具有代表性:

function countOnline(allUsers) {
  let numOnline = 0;
  for(const user in allUsers){
    if(allUsers[user].online) {
      numOnline++;
    }
  }
  return numOnline;
}

其逻辑只有四步:

代码 作用
1 let numOnline = 0; 初始化计数器,必须在循环外声明,否则每次迭代会被重置
2 for(const user in allUsers){ const 声明循环变量 user,每次迭代绑定当前用户键名
3 if(allUsers[user].online) 通过方括号拿到该用户的状态对象,再读取 online 布尔值
4 numOnline++; 命中即累加,最后在循环结束后 return numOnline

值得注意的写法要点:

  • const 声明循环变量:每次迭代 user 实际上是一个新的绑定,因此 const 完全合法,也符合"循环内不改键名"的习惯(本关 hint 的正则也同时接受 varletconst 三种声明方式)。
  • 布尔值直接作为条件:因为题目已保证 online 严格为 true/falseif(allUsers[user].online) 等价于 if(allUsers[user].online === true)
  • 返回值在循环体外return 若写在循环内会在第一次迭代就提前退出,必须放在循环结束后统一返回累计结果。

把该函数放进初始代码运行,会打印 1,因为示例数据中只有 Jeff 在线。若把种子数据替换成 Alan、Sarah 都在线,则输出 2;三人全离线则输出 0

五、自动化测试如何验证你的实现

freeCodeCamp 关卡内的每一段 hints(测试断言)都会被课程测试运行器执行,作为"通关"判据。本关一共挂了四条断言,第一条约束"实现方式",后三条用三种不同数据验证"输出正确性":

1. 必须真的使用 for...in

assert.match(
  __helpers.removeJSComments(code),
    /for\s*\(\s*(var|let|const)\s+[a-zA-Z_$]\w*\s+in\s+[a-zA-Z_$]\w*\s*\)/
);

它先把用户代码中的注释移除(__helpers.removeJSComments),再用正则检查是否存在形如 for (var|let|const 变量名 in 对象名) 的写法。这说明本关考核的核心目标就是"用 for...in 这一具体语法",用 Object.keys() 加数组方法或 for...of 即使能算出结果也无法通过本关——它属于下一关卡 Generate an Array of All Object Keys with Object.keys() 的训练范畴。

2~4. 三种输入三种期望输出

输入对象 在线用户 期望返回值
{ Alan: false, Jeff: true, Sarah: false } 仅 Jeff 1
{ Alan: true, Jeff: false, Sarah: true } Alan、Sarah 2
{ Alan: false, Jeff: false, Sarah: false } 0

三组数据覆盖了"1 人在线、2 人在线、0 人在线"三类典型场景,防止学习者写出只对单一数据有效的硬编码逻辑。这些断言直接内嵌于关卡 Markdown 的 # --hints-- 区块,与 description、seed、solution 一起构成了一个"可独立执行验证"的完整课程单元;若你在仓库里逐字阅读本关文件 587d7b7d367417b2b2512b1d.md,可以看到 --description----instructions----hints----seed----solutions-- 的完整分区结构。

六、for...in 的适用边界与更好替代

需要强调的是,freeCodeCamp 课程在"基础数据结构"阶段刻意先教 for...in,是为了让你理解"对象本质上是键值对集合、可被逐一枚举"这一心智模型。但在现代 JavaScript 工程实践中,需要结合场景选择合适的遍历手段:

  • 遍历对象自身的键并逐个取值for...in 简单直接,但要注意它会连同继承的可枚举属性一起遍历。若只想遍历对象自有属性,通常配合 hasOwnProperty 判断(参考 Check if an Object has a Property),或直接用 Object.keys(obj) 拿到自有键数组再遍历(见 Generate an Array of All Object Keys with Object.keys())。
  • 只关心对象的值Object.values() 更直接;同时需要键和值做映射变换Object.entries() 结合 for...of 或数组方法更常用。
  • 遍历数组for...of 或数组自带方法(forEach/map/filter/reduce)才是正确选择,for...in 会把数组下标当作字符串键来枚举,语义并不贴合。

回到本关的计数场景,for...in + 计数器累加的方式,本质上等价于"过滤出满足条件的键并统计个数"——这正是后续学习 filter/reduce 等函数式写法时反复出现的思想雏形。

七、课程定位:本关在 Basic Data Structures 模块中的位置

本关属于 freeCodeCamp 课程体系 JavaScript 认证下的 Basic Data Structures 模块。在该模块的组织文件 curriculum/structure/blocks/basic-data-structures.json 中,它以 id: 587d7b7d367417b2b2512b1d 登记在"对象操作"子序列中,前承"检查对象是否拥有属性"(b1c),后启"用 Object.keys() 生成键数组"(b1e),整体顺序为:数组基础操作 → 数组迭代与多维数组 → 对象增删改查 → 对象键遍历(本关) → 对象键数组化 → 对象内数组修改。这一编排清晰呈现了"从面向已知键的读写,走向面向未知键集合的遍历处理"的教学梯度。

从实现层面看,本关头部元数据标注 challengeType: 1。根据仓库类型定义 packages/shared/src/config/challenge-types.tschallengeType: 1 对应经典"代码补全 + 测试判定"型关卡,会以 tests 模式运行内置断言来判定学习者是否通关。换言之,你在编辑器中看到的"运行测试 → 通过所有断言 → 完成"的交互,其判定依据就是本文第五节剖析的 # --hints-- 断言集合。

八、小结

本关用一道"在线用户统计"任务,把三个知识点缝合在了一起:

  1. for...in 的语法与语义——为对象每个键执行一次循环体,循环变量就是当前键名;
  2. 方括号动态取值——obj[key] 让"运行时才知道的键"可以被安全读取,这是上一关知识的直接复用;
  3. 无顺序保证的枚举心智——对象键不像数组下标那样有序,代码逻辑必须与迭代顺序无关。

写对 countOnline 只是起点,真正重要的是建立"对象是键值对集合,可遍历、可聚合"的数据结构思维。当你继续推进到 Object.keys()Object.values()Object.entries() 与现代数组方法时,会发现它们都是对这里 for...in 能力的进一步封装与强化。

登录后查看全文
热门项目推荐
相关项目推荐