首页
/ freeCodeCamp Basic Data Structures 挑战精讲:使用 splice() 在数组中一次性完成删除与插入

freeCodeCamp Basic Data Structures 挑战精讲:使用 splice() 在数组中一次性完成删除与插入

2026-09-06 18:30:39作者:胡易黎Nicole

导读

本文以 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);

执行过程逐句拆解:

  1. 初始数组为 [10, 11, 12, 12, 15],五个元素的下标分别是 0 ~ 4
  2. startIndex = 3,即从下标 3 开始操作,该位置的元素是第二个 12
  3. amountToDelete = 1,表示删除 1 个元素,即把下标 3 上的 12 移除;
  4. 剩余参数 13, 14 会从下标 3 这个位置开始依次插入。

因此最终 numbers 变为 [10, 11, 12, 13, 14, 15]——第二个重复的 121314 顶替,实现了"用一个或多个新元素快速替换某个(或某段)旧元素"的效果。这正是本挑战想在说明部分强调的核心价值:一个调用即可完成"替换区间"操作,无需先删后插两步

值得注意的细节是: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)));

只要代码中出现 shiftunshift 字样(比如写成 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 索引的,前两个元素下标是 01,而非 12 删除位置错误,结果数组不符预期
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: 1js 类型,定义见 challenge-types.ts)下测试断言的实际执行方式——既能解对题,也能看懂判题规则背后的设计意图。

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