freeCodeCamp Basic Data Structures 挑战精讲:使用 splice() 在数组中一次性完成删除与插入
导读
本文以 freeCodeCamp 开源课程库中 "Add Items Using splice()" 这一道 JavaScript 基础挑战为骨架,完整讲解 Array.prototype.splice() 的三个及更多参数的用法——如何用一个方法调用同时完成"从指定位置删除元素"和"在同一位置插入新元素"。文中不仅逐条拆解原挑战的说明、初始代码与自动化测试规则,还结合该挑战在课程库中的真实文件位置与挑战类型定义,帮助你理解 freeCodeCamp 挑战文件的组织方式,并能在本地复现与验证解法。
挑战在课程中的位置
本挑战对应课程文件 587d78b3367417b2b2512b11.md,属于 JavaScript 算法与数据结构(JavaScript Algorithms and Data Structures)超级块中的 "Basic Data Structures" 模块。在超级块结构文件 javascript-algorithms-and-data-structures.json 中,basic-data-structures 紧排在 debugging 之后、basic-algorithm-scripting 之前,是学习数组、对象等基础数据结构能力的核心模块。
从模块定义文件 basic-data-structures.json 可以看到,该模块共编排了 20 道挑战,构成了一个循序渐进的学习链条:
- "Add Items to an Array with push() and unshift()" —— 首尾添加
- "Remove Items from an Array with pop() and shift()" —— 首尾删除
- "Remove Items Using splice()" —— 用
splice()从任意位置删除 - "Add Items Using splice()"(本文挑战) —— 用
splice()边删边加 - "Copy Array Items Using slice()"、"Copy an Array with the Spread Operator" 等后续挑战
也就是说,本挑战直接承接其前一道挑战 "Remove Items Using splice()"(课程文件 587d78b2367417b2b2512b10.md)所讲授的 splice() 前两个参数,在其基础上引入"第三个及更多参数",是理解 splice() 完整能力的关键一环。
另外,从挑战文件的 front matter 可以看到本挑战的属性:
| 字段 | 值 | 含义 |
|---|---|---|
id |
587d78b3367417b2b2512b11 |
全局唯一的挑战 ID,模块文件中通过该 ID 编排顺序 |
challengeType |
1 |
挑战类型,对应共享配置 challenge-types.ts 中的 js = 1,即经典 JavaScript 代码挑战(需要根据题目编写代码并通过测试断言) |
forumTopicId |
301152 |
论坛讨论帖关联 ID |
dashedName |
add-items-using-splice |
URL 中使用的短横线命名 |
核心知识点:splice() 的第三个及后续参数
基本签名回顾
Array.prototype.splice() 是数组的原地(in-place)修改方法,其最常用的签名是:
array.splice(startIndex, deleteCount, ...itemsToAdd)
startIndex:整数,表示从哪个下标开始删除(数组是 0 下标索引的,第一个元素下标为 0);deleteCount:要连续删除的元素个数;- 之后的任意个参数
...itemsToAdd:从startIndex处开始插入的元素,可以是 1 个,也可以是任意多个,彼此用逗号分隔。
被删除的元素会以一个新数组的形式作为 splice() 的返回值返回(若没有删除任何元素则返回空数组)。
原挑战给出的完整示例
课程描述中用一个十分直观的例子演示了"边删边加":
const numbers = [10, 11, 12, 12, 15];
const startIndex = 3;
const amountToDelete = 1;
numbers.splice(startIndex, amountToDelete, 13, 14);
console.log(numbers);
执行过程逐句拆解:
- 初始数组为
[10, 11, 12, 12, 15],五个元素的下标分别是0 ~ 4; startIndex = 3,即从下标 3 开始操作,该位置的元素是第二个12;amountToDelete = 1,表示删除 1 个元素,即把下标 3 上的12移除;- 剩余参数
13, 14会从下标 3 这个位置开始依次插入。
因此最终 numbers 变为 [10, 11, 12, 13, 14, 15]——第二个重复的 12 被 13、14 顶替,实现了"用一个或多个新元素快速替换某个(或某段)旧元素"的效果。这正是本挑战想在说明部分强调的核心价值:一个调用即可完成"替换区间"操作,无需先删后插两步。
值得注意的细节是:amountToDelete 之后可以跟任意数量的插入元素(用逗号分隔),它们都会被插入。甚至可以设置 amountToDelete = 0,此时 splice() 退化为"纯插入"工具(在指定位置前插入新元素而不删除任何元素),这一变形在后续实现"在数组中间精确插入"时非常实用。
与删除场景的关系
回顾前一道挑战 Remove Items Using splice() 中的内容:只用前两个参数 splice(startIndex, deleteCount) 即可从数组任意位置删除任意数量的连续元素,例如:
let array = ['today', 'was', 'not', 'so', 'great'];
array.splice(2, 2); // 从下标 2 开始删 2 个
// array === ['today', 'was', 'great']
let removed = array.splice(3, 2); // 返回值是被删除的元素数组
// removed === ['really', 'happy']
本挑战正是在此基础上把"删除"与"插入"合二为一:前两个参数负责"在哪个位置删除多少",其后的所有参数则负责"在同一位置补入什么"。理解了这两道挑战的组合,就完整掌握了 splice() 的经典用法。
实战任务:重写 htmlColorNames 函数
题目要求
课程描述给出的任务是:已经定义了一个函数 htmlColorNames,它接收一个 HTML 颜色名数组作为参数。请仅使用 splice() 删除该数组的前两个元素,并让 'DarkSalmon' 与 'BlanchedAlmond' 分别占据它们原来的位置。
初始种子代码如下(取自原文档 --seed-- 部分):
function htmlColorNames(arr) {
// Only change code below this line
// Only change code above this line
return arr;
}
console.log(htmlColorNames(['DarkGoldenRod', 'WhiteSmoke', 'LavenderBlush', 'PaleTurquoise', 'FireBrick']));
也就是说,期望的输出结果是把开头两个颜色 'DarkGoldenRod'、'WhiteSmoke' 就地换成 'DarkSalmon'、'BlanchedAlmond',其余元素保持顺序不变。
解题思路
'DarkGoldenRod' 与 'WhiteSmoke' 是数组前两个元素,下标分别为 0 和 1。因此:
- 起始下标
startIndex = 0; - 需要删除的元素数量
deleteCount = 2; - 紧接着传入替换元素
'DarkSalmon'与'BlanchedAlmond'。
由于 splice() 修改的是数组自身,删除与插入在同一个调用内完成,最后原样 return arr 即可:
function htmlColorNames(arr) {
arr.splice(0, 2, 'DarkSalmon', 'BlanchedAlmond');
return arr;
}
console.log(
htmlColorNames(['DarkGoldenRod', 'WhiteSmoke', 'LavenderBlush', 'PaleTurquoise', 'FireBrick'])
);
// 输出:['DarkSalmon', 'BlanchedAlmond', 'LavenderBlush', 'PaleTurquoise', 'FireBrick']
为什么这道题必须(或者说最适合)用 splice()?
- 使用
shift()每次只能从头部移除 1 个元素,且无法携带插入内容,想完成本题需要shift()+ 多次unshift()的笨拙组合,还会使代码逻辑碎片化; - 使用下标直接赋值(如
arr[0] = 'DarkSalmon')虽然能改值,但只能"覆盖"而做不到"删除两个再插入两个"的语义,且本挑战的测试规则明确禁止这种写法(详见下文测试部分)。
splice(0, 2, ...) 一行代码同时表达"从头部删掉 2 个 + 原地补入 2 个",语义清晰、原子性强,正是这一 API 的设计意图所在。
在本地验证结果
只需要在任意支持 JavaScript 的运行时(Node.js、浏览器 DevTools 控制台)中粘贴上面的完整函数与 console.log 调用即可运行。也可以把函数定义与调用保存为 .js 文件后用 node 执行,输出应当严格等于:
[ 'DarkSalmon', 'BlanchedAlmond', 'LavenderBlush', 'PaleTurquoise', 'FireBrick' ]
自动化测试:挑战的校验规则详解
freeCodeCamp 的经典 JavaScript 挑战(challengeType: 1)会在浏览器端执行用户代码,并运行挑战文件 --hints-- 部分定义的一组断言来判定是否通过。本挑战共包含四条测试规则,逐条理解它们能帮你避开隐蔽的踩坑点。
1. 返回值必须完全等于期望数组
assert.deepEqual(
htmlColorNames([
'DarkGoldenRod',
'WhiteSmoke',
'LavenderBlush',
'PaleTurquoise',
'FireBrick'
]),
[
'DarkSalmon',
'BlanchedAlmond',
'LavenderBlush',
'PaleTurquoise',
'FireBrick'
]
);
使用 deepEqual 做深比较,意味着不仅顺序要一致,每个元素的字符串值也必须完全一致(注意大小写敏感,例如必须写 'DarkSalmon' 而不能写 'darksalmon')。
2. 必须使用 splice()
assert(/.splice/.test(__helpers.removeJSComments(code)));
该断言通过正则 /\.splice/ 在去除注释后的代码(__helpers.removeJSComments)中检查是否调用了 .splice()。这也暗示了一个常见陷阱:把代码写进注释里并不能蒙混过关——注释会在检查前被移除。
3. 禁止使用 shift() 与 unshift()
assert(!/shift|unshift/.test(__helpers.removeJSComments(code)));
只要代码中出现 shift 或 unshift 字样(比如写成 array.shift()),该断言即失败。它强制你体会 splice() 相对于首尾增删方法的表达优势。
4. 禁止使用数组下标赋值
assert(!/\[\d\]\s*=/.test(__helpers.removeJSComments(code)));
这条正则精确匹配 [数字] 后紧跟赋值号 =(中间允许空白),例如 arr[0] = ... 会被识别并判负。换言之,题目要求你"删除 + 插入",而不是"按下标覆盖值"。
把上述四条规则与函数体对应来看,最直接的满足方式是让函数体只包含一行 arr.splice(0, 2, 'DarkSalmon', 'BlanchedAlmond');。这一校验"由需求到断言"的映射方式,也展示了 freeCodeCamp 挑战文件的整体写法:--description-- 讲概念、--instructions-- 下任务、--hints-- 做自动判定、--seed-- 提供初始代码、--solutions-- 存放官方参考答案。
官方参考答案
原文档的 --solutions-- 中给出的官方实现为:
function htmlColorNames(arr) {
arr.splice(0, 2, 'DarkSalmon', 'BlanchedAlmond');
return arr;
}
这与上文推导的思路完全一致:splice(0, 2, ...) 从下标 0 开始删除 2 个元素,并在同一位置插入两个新颜色名。
易错点与延伸思考
易错点清单
| 易错情形 | 说明 | 后果 |
|---|---|---|
| 起始下标算错 | 数组是 0 索引的,前两个元素下标是 0 和 1,而非 1 和 2 |
删除位置错误,结果数组不符预期 |
deleteCount 填错 |
要"换掉"2 个元素就必须 deleteCount = 2 |
旧元素未被完全移除,会出现多余残留 |
| 拼写/大小写错误 | 颜色名为大小写敏感的字符串字面量 | deepEqual 深比较失败 |
| 把答案写进注释 | __helpers.removeJSComments 会先剥离注释再检测 .splice |
"使用了 splice" 的断言无法命中 |
混用 shift/unshift 或下标赋值 |
触发 --hints-- 中的反例正则 |
直接判负 |
splice() 的其他实用边界
从题目描述与 splice() 的完整语法出发,还可以进一步扩展理解:
- 参数个数可变:
splice()的第三个及之后参数是"可变参数",插入元素数量不限。上面的替换操作中删除 2 个、插入 2 个,二者数量恰好相等;但实际上插入数量可以大于、小于甚至不等于删除数量,数组长度会随之增减。 - 纯插入:当
deleteCount = 0时不删除任何元素,只在startIndex处插入,可用于在数组中间精确安插元素。 - 返回值:
splice()始终返回被删除元素组成的新数组,即使只用于插入(删除数为 0),返回值也是空数组[]。 - 原地修改:与
slice()(不修改原数组、返回副本)不同,splice()会直接改写调用它的数组本身。本例中正是利用这一特性,让htmlColorNames原地改造传入的arr后直接return arr。
在模块后续课程中,你还会遇到 slice()、展开运算符(Spread Operator)等"复制/组合"类 API(见 basic-data-structures.json 的挑战顺序)。把它们与原地修改的 splice() 放在一起对比记忆,能帮你建立清晰的"修改原数组 vs. 返回新数组"心智模型,这在处理状态不可变性的工程场景中尤为重要。
小结
通过本挑战你掌握了一个高频且高效的数组操作技巧:splice(startIndex, deleteCount, ...items) 能够在单个原子调用中完成"定位删除 + 原位插入",非常适合用新值替换数组中的一段连续元素。同时,通过阅读该挑战在 freeCodeCamp 课程库 中的原始文件结构,你也理解了 --description--、--instructions--、--hints--、--seed--、--solutions-- 各区块的分工,以及 challengeType: 1(js 类型,定义见 challenge-types.ts)下测试断言的实际执行方式——既能解对题,也能看懂判题规则背后的设计意图。
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 StartedRust0627
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