首页
/ freeCodeCamp 基础数据结构教程:用方括号记法(Bracket Notation)按变量名动态访问对象属性

freeCodeCamp 基础数据结构教程:用方括号记法(Bracket Notation)按变量名动态访问对象属性

2026-09-06 18:44:42作者:吴年前Myrtle

本教程以 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];

其执行过程分两步:

  1. 第一行先通过 getCurrentFood(scannedItem) 得到扫描商品的名称,并存入变量 selectedFood
  2. 第二行 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"),并断言返回 251327。三点选取覆盖了对象字面量中的首、中、末位置键,足以证明实现是"按传入参数动态取键",而非某种针对单一键的写死逻辑——例如若错误地写成 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.jsonchallengeOrder 可知其前后脉络:

整个模块又隶属于 "JavaScript Algorithms and Data Structures" 超级模块,见 curriculum/structure/superblocks/javascript-algorithms-and-data-structures.jsonblocks 数组。

在真实 JavaScript 工程中,"用变量名动态取对象值"几乎无处不在:查表驱动的算法、从 event.target.name 读取表单字段后去配置对象取值、根据后端返回的状态码映射文案等。方括号记法并不是"取巧技巧",而是对象动态访问的基础语法——它与点记法各司其职:

  • 点记法 obj.key:适用于键名是合法标识符且写代码时已知的静态场景,简洁直观;
  • 方括号记法 obj[expr]:键名含空格/特殊字符、或键名来自变量/表达式(本文核心)时使用,对括号内内容先求值再访问。

要点速览

  • 方括号内的内容是一个表达式obj[someVariable] 会先读取 someVariable 的值,再以该值为键访问对象;
  • 键不存在时,方括号访问返回 undefined,可用于访问前的存在性判断;
  • 点记法无法做到按变量访问——obj.someVariable 访问的是名为 someVariable 的静态键;
  • 本挑战的正确解是在 checkInventoryreturn foods[scannedItem];,通过传入不同键名验证动态取值的正确性。
登录后查看全文
热门项目推荐
相关项目推荐