freeCodeCamp 基础数据结构教程:用方括号记法(Bracket Notation)按变量名动态访问对象属性
本教程以 freeCodeCamp 开源仓库中 JavaScript 基础数据结构(Basic Data Structures)模块的挑战 "Access Property Names with Bracket Notation"(curriculum/challenges/english/blocks/basic-data-structures/587d7b7c367417b2b2512b1a.md)为主线,讲解对象属性访问的两大核心记法在动态场景下的取舍:点记法(dot notation)为何无法满足"属性名来自运行时变量"的需求,以及方括号记法如何通过对表达式求值来按需读取任意键的值。学完本文,你将能在收银台库存查询、配置表取值、表单字段映射等真实场景中写出健壮的动态属性访问代码,并读懂 freeCodeCamp 仓库中该挑战的 --hints-- 测试断言是如何验证学习成果的。
为什么需要"按变量访问属性":从超市收银台说起
对象(Object)是 JavaScript 中最基础的数据结构之一,本质上是"键值对"(key-value pairs)的集合——每一段数据(value)映射到一个称为"属性/键"的唯一标识符。它天然适合表达"名称 → 数量"这种字典式映射,例如一个超市的库存表:
let foods = {
apples: 25,
oranges: 32,
plums: 28,
bananas: 13,
grapes: 35,
strawberries: 27
};
原挑战描述(--description-- 部分)给出了一个极具代表性的使用场景:这个 foods 对象正被用于一个超市收银台程序中。收银台扫描一件商品后,程序会通过某个函数得到当前商品的名称(如 "apples"),接着需要在 foods 对象中查询该商品是否在库存列表里、余量是多少。
问题在于:扫描结果是一个"在运行时才产生、且不断变化"的变量,而不是一个在写代码时就能敲死属性名。如果商品名称 "apples" 是静态写死的,直接写 foods.apples 没问题;可当商品名称来自扫描枪输入时,用点记法写 foods.selectedFood 就完全错了——它访问的将是一个名为 selectedFood 的键,而不是变量里存的那个字符串。
方括号记法求值原理:变量内容决定访问哪个键
此时需要方括号记法。原挑战中的关键示例代码是:
let selectedFood = getCurrentFood(scannedItem);
let inventory = foods[selectedFood];
其执行过程分两步:
- 第一行先通过
getCurrentFood(scannedItem)得到扫描商品的名称,并存入变量selectedFood; - 第二行
foods[selectedFood]先对括号内的表达式selectedFood求值,取出其当前存储的字符串值(例如"bananas"),再以该字符串作为键去foods中查找并返回对应值(例如13)。
这与点记法形成鲜明对比:foods.selectedFood 中的 selectedFood 是字面量标识符,永远不会被"替换"为变量的值;而 foods[selectedFood] 中的 selectedFood 是表达式,其值决定实际访问的键名。
该机制的两个直接推论,正是方括号记法的核心价值所在:
- 属性名在运行时才确定:当对象属性不是写代码时就能预知的静态名称,而是来自用户输入、网络响应、配置文件或程序内部状态时,只能通过方括号记法间接访问;
- 返回语义清晰:若变量值对应的键不存在,访问表达式返回
undefined。原挑战明确指出这一行为——用它可以在访问前判断"这个键到底存不存在"。这与同模块后续挑战 Check if an Object has a Property 中关于属性存在性检查的话题一脉相承。
补充:关于方括号记法在写入侧的运用,可回顾本模块前一挑战 Add Key-Value Pairs to JavaScript Objects。那里说明了两条必须用方括号记法的规则:属性名含空格(如
tekkenCharacter['hair color'] = 'dyed orange')时,必须以带引号的字符串形式书写;以及用变量作属性名时(如tekkenCharacter[eyes] = 'brown',其中eyes = 'eye color'),不带引号的标识符会被当作变量求值。本文讨论的"读取"场景正是同一求值规则的延伸。
动手实践:完成 checkInventory 挑战
原挑战的 --instructions-- 给出了明确的编程任务:
我们定义了一个函数
checkInventory,它接收一个扫描商品(scanned item)作为参数。请返回foods对象中scannedItem这个键对应的当前值。可以假设传入checkInventory的只会是合法键。
即:把"根据传入键名,从 foods 中动态取值"的逻辑实现到函数体内。挑战的起始代码(--seed-- 的 --seed-contents--)如下:
let foods = {
apples: 25,
oranges: 32,
plums: 28,
bananas: 13,
grapes: 35,
strawberries: 27
};
function checkInventory(scannedItem) {
// Only change code below this line
// Only change code above this line
}
console.log(checkInventory("apples"));
参考答案与关键一行
参考解(--solutions-- 部分)只有一个关键改动——在函数体内返回方括号访问的结果:
function checkInventory(scannedItem) {
return foods[scannedItem];
}
这里的 scannedItem 就是形参(参数变量):当调用 checkInventory("bananas") 时,scannedItem 被赋值为字符串 "bananas",foods[scannedItem] 求值后等价于 foods["bananas"],返回 13。
有一点值得注意:--solutions-- 中的参考答案与其 --seed-- 有一处细微差异——foods 的声明从 let 保持了不变,而函数声明从"后置"变成先于 console.log,结构上更接近可独立运行的完整程序。无论你采用哪种声明顺序,只要函数体内正确返回 foods[scannedItem],即可通过全部测试。这也提醒读者:初学时保持种子代码原样、只改动 // Only change code below this line 与注释上方的区间,是最稳妥的做法。
边界行为与假设
原挑战的指令特意声明了前提:"可以假设传入 checkInventory 的只会是合法键(valid keys)",因此解题时无需处理键不存在的情况。但根据 --description-- 中的说明,若真的传入了非法键(如 checkInventory("watermelon")),方括号访问会返回 undefined——这正是方括号表达式"找不到就返回 undefined"的本征行为。理解这一点,可以为后续阅读 freeCodeCamp 中关于属性存在性检查的挑战(Check if an Object has a Property)做好铺垫。
测试验证:读懂 --hints-- 断言
freeCodeCamp 的每道挑战都内置自动化测试(--hints-- 段),用来逐条验证学习者的代码。本挑战共 5 条断言,正好构成对"动态取值"功能的完整验收清单:
| 断言含义 | 测试代码 | 验证要点 |
|---|---|---|
checkInventory 必须是函数 |
assert.strictEqual(typeof checkInventory, 'function') |
函数已被正确定义 |
foods 对象内容不被破坏 |
assert.deepEqual(foods, { apples: 25, oranges: 32, ... }) |
原对象仍保有全部 6 个键值对 |
| 按键取数正确性(三类样本) | assert.strictEqual(checkInventory('apples'), 25) 等 |
分别验证首部、中部、尾部键的动态查询 |
第 3 组断言尤为关键:它分别调用 checkInventory("apples")、checkInventory("bananas")、checkInventory("strawberries"),并断言返回 25、13、27。三点选取覆盖了对象字面量中的首、中、末位置键,足以证明实现是"按传入参数动态取键",而非某种针对单一键的写死逻辑——例如若错误地写成 return foods.apples,只会通过第一条、而后两条必然失败。
从仓库实现层面看,这些测试断言最终由 freeCodeCamp 前端的挑战评测运行器执行;作为内容承载单元,该挑战的元数据同时记录了其编号 id: 587d7b7c367417b2b2512b1a、类型 challengeType: 1(普通编程题)与 dashedName: access-property-names-with-bracket-notation,这些字段被 curriculum/structure/blocks/basic-data-structures.json 中的 challengeOrder 引用以决定课程排序。你可以把验证代码单独复制到浏览器控制台或 Node REPL 中运行,直观观察每个断言通过与否。
本挑战在课程与真实代码中的位置
本挑战位于 Basic Data Structures 模块中"对象操作"序列的中间环节。查看 curriculum/structure/blocks/basic-data-structures.json 的 challengeOrder 可知其前后脉络:
- 前置挑战 Add Key-Value Pairs to JavaScript Objects:讲解用方括号记法新增含空格或变量名的属性,本文是其求值规则的"读取侧"延伸;
- 前置挑战 Modify an Object Nested Within an Object:讲解嵌套对象与多层属性链访问;
- 本挑战(
587d7b7c...1a):动态读取属性; - 后续挑战 Use the delete Keyword to Remove Object Properties:删除属性;
- 后续挑战 Check if an Object has a Property:判断属性是否存在。
整个模块又隶属于 "JavaScript Algorithms and Data Structures" 超级模块,见 curriculum/structure/superblocks/javascript-algorithms-and-data-structures.json 的 blocks 数组。
在真实 JavaScript 工程中,"用变量名动态取对象值"几乎无处不在:查表驱动的算法、从 event.target.name 读取表单字段后去配置对象取值、根据后端返回的状态码映射文案等。方括号记法并不是"取巧技巧",而是对象动态访问的基础语法——它与点记法各司其职:
- 点记法
obj.key:适用于键名是合法标识符且写代码时已知的静态场景,简洁直观; - 方括号记法
obj[expr]:键名含空格/特殊字符、或键名来自变量/表达式(本文核心)时使用,对括号内内容先求值再访问。
要点速览
- 方括号内的内容是一个表达式:
obj[someVariable]会先读取someVariable的值,再以该值为键访问对象; - 键不存在时,方括号访问返回
undefined,可用于访问前的存在性判断; - 点记法无法做到按变量访问——
obj.someVariable访问的是名为someVariable的静态键; - 本挑战的正确解是在
checkInventory内return foods[scannedItem];,通过传入不同键名验证动态取值的正确性。
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 StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
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