freeCodeCamp Basic JavaScript 挑战精讲:用点号与方括号访问嵌套对象(Accessing Nested Objects)
导读
本篇文章以 freeCodeCamp 开源课程中 Basic JavaScript(基础 JavaScript)模块的 "Accessing Nested Objects" 挑战为讲解主线,完整剖析 JavaScript 中嵌套对象的访问规则、点号与方括号两种语法的适用边界,并手把手拆解该挑战的初始代码、隐藏测试断言与标准解法。读完本文,你将不仅能独立通过这道题,还能理解 freeCodeCamp 挑战文件从 description、hints 到 seed、solutions 的完整组织结构,把“做题”上升为“读懂课程源码”。该挑战位于本仓库的 curriculum/challenges/english/blocks/basic-javascript/56533eb9ac21ba0edf2244cc.md,属于 JavaScript 对象体系“从入门到精通”序列中的一环。
一、挑战定位:Basic JavaScript 对象模块中的关键一环
本挑战的文件名 56533eb9ac21ba0edf2244cc.md 位于 curriculum/challenges/english/blocks/basic-javascript/ 目录,其中 basic-javascript 即“块”(Block)名。在同一块的课程顺序文件 curriculum/structure/blocks/basic-javascript.json 中,可以看到它前后依次排布着紧密衔接的知识点:
Accessing Object Properties with Dot Notation(点号访问对象属性)Accessing Object Properties with Bracket Notation(方括号访问对象属性)Accessing Object Properties with Variables(用变量访问对象属性)Updating Object Properties、Add New Properties、Delete PropertiesManipulating Complex Objects(操作复杂对象)Accessing Nested Objects(本挑战)Accessing Nested Arrays(访问嵌套数组)Record Collection(记录集合综合练习)
也就是说,本挑战是在掌握点号与方括号基础语法之后,把二者“链接”起来处理多层对象结构的综合演练,是通向 Record Collection 这类综合性项目的必经之路。
二、核心概念:什么是嵌套对象
在 JavaScript 中,对象属性的值可以是任意类型——包括另一个对象。当一个对象的属性值本身还是对象(甚至可以继续向下嵌套若干层)时,就构成了“嵌套对象(Nested Object)”。
例如挑战描述中给出的示例 ourStorage:
const ourStorage = {
"desk": {
"drawer": "stapler"
},
"cabinet": {
"top drawer": {
"folder1": "a file",
"folder2": "secrets"
},
"bottom drawer": "soda"
}
};
这里共有三层结构:
- 顶层是
desk与cabinet两个属性; cabinet的值又是一个对象,内部含"top drawer"与"bottom drawer";"top drawer"的值仍是对象,内部含folder1与folder2。
访问子属性的方式,就是把点号或方括号逐层“链接”起来,这正是挑战标题与描述的核心:sub-properties of objects can be accessed by chaining together the dot or bracket notation(对象子属性可以通过链式串联点号或方括号来访问)。
三、规则要点:点号与方括号,何时用哪个
要正确解答本挑战,必须先吃透两种访问语法的取舍规则,这也是官方 hints(隐藏测试)所要校验的核心。
3.1 点号记法(Dot Notation)
点号语法适用于符合 JavaScript 合法标识符命名规则的属性名,即名称只能包含字母、数字、下划线(_)与美元符($),且不能以数字开头,也不能包含空格。
ourStorage.desk.drawer; // "stapler"
3.2 方括号记法(Bracket Notation)
当属性名不符合标识符规则时——比如带空格(如 "top drawer"、"glove box")、以数字开头,或需要由变量动态计算时——必须使用字符串形式的方括号记法。挑战示例就同时用到了两种写法:
ourStorage.cabinet["top drawer"].folder2; // "secrets"
ourStorage.desk.drawer; // "stapler"
注意 "top drawer" 必须写在方括号并加引号中,因为 ourStorage.cabinet.top drawer 这种写法在语法上根本无法成立(空格会破坏标识符)。
3.3 混合链式的取值推演
示例表达式逐步求值如下:
ourStorage.cabinet→ 得到cabinet子对象;ourStorage.cabinet["top drawer"]→ 得到folder1/folder2子对象;ourStorage.cabinet["top drawer"].folder2→ 得到字符串"secrets";ourStorage.desk.drawer→ 一路点号下钻,得到"stapler"。
挑战原文据此断言:ourStorage.cabinet["top drawer"].folder2 求值结果是字符串 secrets,ourStorage.desk.drawer 求值结果是字符串 stapler。可见**“链式访问”的求值顺序是从最外层对象开始,一层一层向内取属性值**,每一步的返回值都是下一步的访问目标。
四、本关任务拆解:读取 myStorage 中的手套箱
在掌握上述规则后,我们来看本关的实际任务(instructions):
Access the
myStorageobject and assign the contents of theglove boxproperty to thegloveBoxContentsvariable. Use dot notation for all properties where possible, otherwise use bracket notation.
翻译过来即:从 myStorage 对象中取到 glove box 属性的内容,赋值给变量 gloveBoxContents;能使用点号的地方必须用点号,实在无法使用点号时再用方括号。
4.1 初始代码(seed)
挑战为学习者预置了如下初始代码:
const myStorage = {
"car": {
"inside": {
"glove box": "maps",
"passenger seat": "crumbs"
},
"outside": {
"trunk": "jack"
}
}
};
const gloveBoxContents = undefined;
myStorage 的结构为三层:顶层 car → 中层 inside(还有并列的 outside)→ 内层 "glove box" 与 "passenger seat"。初始代码把 gloveBoxContents 置为 undefined,等待学习者用正确的链式访问替换。
4.2 求解思路
逐层分析 glove box 的访问路径:
- 取顶层
myStorage.car——car是合法标识符,必须用点号; - 取中层
myStorage.car.inside——inside是合法标识符,必须用点号; - 取内层
myStorage.car.inside["glove box"]——"glove box"含空格,无法用点号,必须用方括号字符串。
于是标准解为:
const myStorage = {
"car": {
"inside": {
"glove box": "maps",
"passenger seat": "crumbs"
},
"outside": {
"trunk": "jack"
}
}
};
const gloveBoxContents = myStorage.car.inside["glove box"];
gloveBoxContents 最终将等于字符串 "maps"。若写错顺序(如 myStorage.car["glove box"].inside)则无法命中目标属性;若对 "glove box" 误用点号(myStorage.car.inside.glove box)则直接产生语法错误。
五、隐藏测试解析:仓库如何判定你的代码是否正确
freeCodeCamp 的每道挑战都附有 # --hints-- 段,由框架自动运行断言(assert)来打分。读懂它们,等于拿到官方评分标准。本挑战共有 4 个 hint,全部定义在挑战文件 curriculum/challenges/english/blocks/basic-javascript/56533eb9ac21ba0edf2244cc.md 中。
5.1 取值正确性
assert(gloveBoxContents === 'maps');
要求 gloveBoxContents 严格等于字符串 maps。它直接验证了你访问路径的终点是否真的指向 "glove box" 的值。
5.2 语法规范性(能点号必点号)
assert.match(code, /myStorage\.car\.inside/);
该断言用正则匹配源码文本,检查代码中是否出现了 myStorage.car.inside 这一段点号链。这就在机制上强制你遵守“where possible 尽量用点号”的指令——只要这两层没写对(例如不必要地写成 myStorage["car"]["inside"]),即使最终结果正确,本题也会判错。
5.3 变量声明方式
assert.match(code, /const\s+gloveBoxContents\s*=/);
要求 gloveBoxContents 必须使用 const 声明(不允许改写为 let 或 var)。
5.4 原对象不得改动
const expectedMyStorage = {
"car":{
"inside":{
"glove box":"maps",
"passenger seat":"crumbs"
},
"outside":{
"trunk":"jack"
}
}
};
assert.deepStrictEqual(myStorage, expectedMyStorage);
使用 deepStrictEqual 做深比较,确保初始的 myStorage 对象未被任何方式改动——这意味着你不能为了“取巧”而重写或扁平化对象。
从这些断言可以直观体会到 freeCodeCamp 的评测策略:既要结果正确,也要写法符合教学规范,还保护了初始数据不可篡改。这也是其课程“循序渐进约束练习者行为”的典型体现。
六、源码视角:挑战文件是怎么被解析、校验与执行的
作为一个开源项目,本仓库不仅提供课程文本,还提供一整套解析、校验与执行的实现。理解了这些,你就能从“做题者”升级为“课程研究者”。
6.1 frontmatter 元数据
每个挑战 Markdown 顶部是 YAML frontmatter,本挑战字段如下:
| 字段 | 值 | 含义 |
|---|---|---|
id |
56533eb9ac21ba0edf2244cc |
全局唯一挑战 ID,是课程排序、URL 与引用的主键 |
title |
Accessing Nested Objects |
挑战显示标题 |
challengeType |
1 |
挑战类型,从 packages/shared/src/config/challenge-types.ts 中维护的枚举顺序看,1 对应基础 JavaScript 编程练习类型(可运行 JS 代码并接受评测) |
dashedName |
accessing-nested-objects |
URL 友好名称,同时受 schema 校验约束 |
课程结构文件 curriculum/structure/blocks/basic-javascript.json 正是按 id + title 的形式,以数组顺序记录块内全部挑战,而本挑战 id 56533eb9ac21ba0edf2244cc 恰好出现在该 JSON 的第 375 行前后,前后就是上文提到的那一整套对象练习。
6.2 Schema 校验
挑战文件必须满足 curriculum/schema/challenge-schema.js 中定义的 Joi 约束,例如 challengeType 必须是 0~33 的数字、dashedName 必须匹配 ^[a-z0-9-]+$、solutions 必须是文件对象数组的数组等。其中 description 段是必填字符串,solutions(标准答案)则保存为多个文件快照,供后续测试运行与“显示解决方案”功能使用。
6.3 加载与构建链路
课程文件由 curriculum/src/get-challenges.ts 等加载器读取,随后在构建阶段(见 curriculum/src/build-superblock.ts 第 248 行附近)会检测 challenge.solutions?.length,并把解决方案中的源码文件转换成 polyvinyl 格式供测试运行器使用;文件级结构定义见 curriculum/src/file-handler.ts(其中声明了 solutions?: ChallengeFile[][])。也就是说,你在题解区看到的官方答案,本质上就是该 Markdown # --solutions-- 段被解析、校验并通过构建管线喂给评测系统的产物。
七、常见错误与调试建议
结合题目 hints 与教学意图,罗列本关最易出现的三类错误:
- 对含空格的键误用点号:如
myStorage.car.inside.glove box,会直接触发语法错误。空格键名只能写成["glove box"]。 - 能点号却用方括号:如
myStorage["car"]["inside"],虽然结果正确,但会因不满足正则断言/myStorage\.car\.inside/而无法通过 hint 2。 - 改动初始对象或漏写
const:重排对象结构会触发deepStrictEqual失败;把const改成let/var则会触发 hint 3 失败。
自查清单:结果是否为字符串 maps?访问链是否同时含点号段 myStorage.car.inside 与方括号段 ["glove box"]?声明是否为 const gloveBoxContents = ...?myStorage 是否保持原样?
八、向后续挑战延伸
本挑战是“复杂结构访问”的起点。同一块中紧随其后的 Accessing Nested Arrays 会教你如何在对象与数组交错的结构中继续用链式下标定位元素;而 Record Collection 则要求综合运用“读取 + 更新 + 新增 + 删除”能力维护一个多层记录集合。掌握本挑战的“能点号则点号、否则方括号、逐层下钻”方法论后,再复杂的 JSON/对象结构你都能抽丝剥茧地访问到位。
结语
通过“Accessing Nested Objects”这一挑战,你同时获得了两层收获:作为学习者,你掌握了 JavaScript 嵌套对象链式访问的完整规则与点号/方括号的选择标准;作为开源课程读者,你看到了 freeCodeCamp 如何用 description、instructions、hints、seed、solutions 五段式结构,把“知识讲解 + 语法规约 + 自动化断言 + 起始代码 + 官方答案”封装成一份可版本化管理、可校验、可离线运行的挑战文件。现在,你可以打开 curriculum/challenges/english/blocks/basic-javascript/56533eb9ac21ba0edf2244cc.md 亲自重读原文,并在自己的编辑器中运行上面的示例与解法,亲手验证每一个取值结果。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0630
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00