首页
/ freeCodeCamp 基础 JavaScript 挑战精讲:用多维数组构建 Shopping List 购物清单

freeCodeCamp 基础 JavaScript 挑战精讲:用多维数组构建 Shopping List 购物清单

2026-09-07 09:56:44作者:尤峻淳Whitney

本文围绕 freeCodeCamp 开源仓库中 Basic JavaScript(基础 JavaScript)块内的 Shopping List 编程挑战展开,逐句拆解题目要求、参考解法与自动化测试逻辑,并回到仓库源码与相邻课程文件,弄清"多维数组(multi-dimensional array)"这一核心概念在真实课程体系中的前因后果。读完本文,你将能独立完成该挑战,并掌握用 [名称, 数量] 这样的成对数组结构建模数据的通用思路。

挑战出处:Basic JavaScript 块末尾的一道综合练习

在 freeCodeCamp 的课程仓库中,所有课程都被组织为 Superblock → Block → Challenge 三层结构,每个挑战是一个独立的 Markdown 文件。本次的 Shopping List 挑战位于:

挑战原文件

basic-javascript 块元数据 可以看到,56533eb9ac21ba0edf2244bc(即 Shopping List)在 challengeOrder 中紧跟在 push / pop / shift / unshift 四个数组操作课程之后、Write Reusable JavaScript with Functions(函数入门)之前,是一道典型的学完新知识后进行综合应用的过渡练习;而该块的 blockLayout 被标记为 legacy-challenge-list。整个 basic-javascript 块又是仓库 JavaScript 算法与数据结构 superblock 结构文件 中排在首位(blocks[0])的课程块,其后依次是 es6regular-expressionsdebuggingbasic-data-structures 等,构成了完整的学习主线。

每个挑战文件顶部的 frontmatter 记录了元信息,Shopping List 的结构是:

id: 56533eb9ac21ba0edf2244bc
title: Shopping List
challengeType: 1
forumTopicId: 18280
dashedName: shopping-list

其中 id 是全局唯一标识(同一文件在本仓库的块顺序、链接跳转中都依赖它),dashedName 是 URL 友好形式的课程名。正文则按 --description--(题目描述)、--hints--(自动化测试断言)、--seed--(初始代码)、--solutions--(参考解法)四段组织,这也是理解 freeCodeCamp "课程即代码"理念的入口。

题目拆解:把一段购物需求翻译成数据结构

挑战原文给出的任务非常简短:

Create a shopping list in the variable myList. The list should be a multi-dimensional array containing several sub-arrays. The first element in each sub-array should contain a string with the name of the item. The second element should be a number representing the quantity.

也就是说,需要定义一个名为 myList 的多维数组,其中每个元素又是一个子数组,每个子数组恰好两个位置:

  • 第 0 位(下标 0):字符串,代表商品名称,例如 "Chocolate Bar"
  • 第 1 位(下标 1):数字,代表购买数量,例如 15

原文给出了单个条目的示例形态:

["Chocolate Bar", 15]

并要求整个列表至少包含 5 个子数组("There should be at least 5 sub-arrays in the list")。这本质上是在让你用 JS 原生数据结构表达一张"商品名 + 数量"的二维表格:外层数组是清单本身,内层两元素数组是清单行。

编辑器初始给你的是这样一行种子代码:

const myList = [];

注意种子使用 const 声明而非 var——即使是在 legacy 块中,freeCodeCamp 也默认引导学习者使用块级常量声明;此处 myList 的引用不会被重新赋值,因此 const 完全够用。

前置知识复习:从一维数组到"数组的数组"

要解出这道题,需要先弄清它之前几节课铺垫的三个知识点,仓库中的相邻挑战文件恰好提供了最权威的示例。

第一,数组用方括号字面量存储多个值。Store Multiple Values in one Variable using JavaScript Arrays 中给出的标准写法是:

const sandwich = ["peanut butter", "jelly", "bread"];

这条课程还特别验证了 JS 数组是可异构的容器:同一数组可以混装字符串和数字。

第二,数组可以嵌套在数组里,形成多维数组。 Nest one Array within Another Array 明确定义了术语:teams = [["Bulls", 23], ["White Sox", 45]] 这种形态被称为 multi-dimensional array(多维数组),即"数组的数组"。

第三,用连续的多对方括号逐层取值。 Access Multi-Dimensional Arrays With Indexes 给出了行/列式的取值心智模型:

const arr = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
  [[10, 11, 12], 13, 14]
];

const subarray = arr[3];        // [[10, 11, 12], 13, 14]
const nestedSubarray = arr[3][0]; // [10, 11, 12]
const element = arr[3][0][1];   // 11

第一对方括号定位最外层,之后每多一对方括号就向下深入一层。该课程还给出了一条书写规范提醒:数组名与方括号之间不要加空格,arr[0][0] 可以,arr [0] [0] 虽能被 JS 解析但不推荐。

Shopping List 正是这三节课知识点的收口应用:既要知道子数组形态怎么写,又要理解外层数组把多个条目装在一起的含义。

参考实现:一行行看懂官方答案

仓库里随挑战保存的官方参考解法(见挑战文件末尾的 --solutions-- 段)是:

const myList = [
  ["Candy", 10],
  ["Potatoes", 12],
  ["Eggs", 12],
  ["Catfood", 1],
  ["Toads", 9]
];

逐条核对:

  • 外层 myList 是一个数组,.length 为 5,满足"至少 5 个子数组";
  • 每个内层子数组恰好两个元素,第 0 位是字符串("Candy" 等),第 1 位是数字(1012 等);
  • 5 行相互之间用逗号分隔,最后一行后不加逗号也完全合法。

除了这种一次性字面量写法,也可以先建空数组再用 push 动态追加,效果等价(push 正是本块前一道课程 Manipulate Arrays With push Method 的内容):

const myList = [];
myList.push(["Candy", 10]);
myList.push(["Potatoes", 12]);
myList.push(["Eggs", 12]);
myList.push(["Catfood", 1]);
myList.push(["Toads", 9]);

日常开发中,"先定义再逐行 push"常用于数据来源不确定、需按条件分批追加的场景;而挑战场景下直接写数组字面量最直观、最不易出错。

隐藏关卡:--hints-- 里的四条自动化断言

Shopping List 的"判题标准"完整写在挑战文件的 --hints-- 段里,弄懂它们等于弄懂了题目的全部隐藏约束:

myList 必须真的是数组:

assert(Array.isArray(myList));

Array.isArray 而不是 typeof,是因为 typeof [] 会返回 "object",无法与普通对象区分,这正是 Store Multiple Values 挑战 中不得已用 typeof myArray == 'object' 的原因;到了 Shopping List 阶段,课程刻意改用更严谨的 Array.isArray

② 每个子数组的第 0 位必须是字符串:

assert(myList.every(elem => elem && typeof elem[0] === 'string'));

这里 elem && 的前置守卫值得注意:如果某个元素是 nullundefined 或空数组,先短路为假值使断言通过失败判定,避免直接对空值访问 [0] 抛错。every 要求所有子数组都满足条件。

③ 每个子数组的第 1 位必须是数字:

assert(myList.every(elem => elem && typeof elem[1] === 'number'));

与上一条对称,强制了"数量必须是 number"这一语义约束,"10"(字符串数字)在此会被判错。

④ 至少要有 5 个条目:

assert(myList.length > 4);

等价于"长度必须大于 4",即至少 5 个子数组。注意它是针对外层数组长度的断言,与内层子数组个数直接相关。

综合四条断言,一个合法的 myList 必须是:外层长度 ≥ 5 的数组,且每个内层元素都是长度 ≥ 2、元素 0 为字符串、元素 1 为数字的子数组。 这正是题目描述的机器可校验版本,也体现了 freeCodeCamp 把"自然语言需求 + 可执行断言"打包在同一 Markdown 文件的课程工程化思路。

常见误区与自查技巧

  • 条目不足 5 个length 为 3 或 4 会直接触发第 ④ 条断言失败。数清楚子数组用逗号分隔的个数即可。
  • 字符串与数字写反:如 [10, "Candy"],会同时触发第 ②③ 条失败。始终记住约定顺序是"名称在左(下标 0)、数量在右(下标 1)"。
  • 多包了一层或漏了一层:写成 myList = ["Candy", 10] 则外层元素不是数组,everyelem[0] 会取到字符 "C"(仍可能是 string)但第二条对 elem[1] 就会取到 undefined;而写成 [[["Candy"], 10]] 这类过度嵌套虽然不会全部报错,却偏离了"两元素子数组"的数据契约。
  • 误用对象代替数组{ item: "Candy", qty: 10 } 这类对象字面量不符合子数组结构,会被第 ② 条拒绝。

自查时可以在浏览器控制台或 Node REPL 里快速验证:

console.log(Array.isArray(myList));          // true
console.log(myList.length);                  // >= 5
console.log(myList.every(e => e && typeof e[0] === 'string' && typeof e[1] === 'number')); // true

延伸:这一模式在后续课程里反复出现

[label, value] 这种"成对数组"结构在本块乃至后续块中被反复使用:

  • 紧随其后的 Accessing Nested ArraysNesting For Loops 会要求你用双层循环或连续下标遍历二维结构;
  • 更后面的 es6basic-data-structures 等块(同见 superblock 结构文件)则会引入 Map、解构赋值等更高级的键值容器——购物清单挑战是理解它们之前的"最小可行数据模型"。

真正掌握这道题的标准是:看到"若干行、每行两个字段"的数据,能下意识地用 [[a, b], [c, d], ...] 建模,并能用 myList[row][col] 读取任意格子。

如何在仓库中复现与继续深挖

本仓库就是 freeCodeCamp 的开源代码与课程库,所有课程文件均以 Markdown + YAML frontmatter 形式存放,可以按以下路径研读:

此外,从仓库结构看,curriculum/schema 目录下维护着挑战与课程结构的 schema 定义及配套校验测试(如 challenge-schema.jscurriculum-schema.js),如果你希望深入理解挑战文件格式如何被机器校验,这是很好的下一站;curriculum/src/test 下也沉淀了课程级测试工具链。总而言之,Shopping List 虽是一道入门练习,但它完整承载了"题目描述—初始代码—测试断言—官方解法"四段式课程文件的结构,吃透它,你既能收获多维数组的实战手感,也掌握了继续阅读 freeCodeCamp 数千道课程文件的格式钥匙。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.79 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
390