☰
Modern JavaScript Tutorial 精讲:用 splice 实现数组区间过滤 filterRangeInPlace(原地修改)
2026/10/3 1:57:39 网站建设 项目流程
  • 文档/教程
  • 前端

【免费下载链接】en.javascript.info

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载

导读

本文围绕 Modern JavaScript Tutorial(en.javascript.info 仓库) 中"数组方法(Array methods)"一节的经典习题Filter range "in place"展开。题目要求编写filterRangeInPlace(arr, a, b):在不新建数组的前提下,从原数组中移除所有不在闭区间[a, b]内的元素,并且函数不返回任何值。读完本文你将掌握:如何利用splice在正向遍历中安全删除元素、为什么删除后要手动执行i--、它与基于filter的非破坏性版本filterRange的本质区别,以及仓库中配套的 Mocha 测试如何验证正确性。


一、题目回顾:原地过滤的完整定义

原题出自 task.md,题目难度标记为importance: 4(中等偏上),核心要求如下:

编写函数filterRangeInPlace(arr, a, b),接收数组arr,移除其中所有不在a与b之间的值。判定条件是:a ≤ arr[i] ≤ b。 函数只能修改传入的数组,不能返回任何东西。

示例行为:

let arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 1, 4); // 移除 1 到 4 之外的数字 alert( arr ); // [3, 1]

从示例可以看到两个关键约束:

  1. 原地修改(in place):arr自身被改变,调用后只剩下区间内的元素[3, 1];
  2. 无返回值:函数修改的是外部传入的数组引用,因此无需(也不应该)返回新数组。

这一约束与同章节的姊妹题 Filter range(非原地版) 形成鲜明对照:filterRange要求"不修改数组、返回新数组",而filterRangeInPlace则要求"修改原数组、不返回任何东西"。这两题组合起来,恰好覆盖了 JavaScript 中"变换数组"的两种基本范式:新建并返回与就地改写。


二、参考解法逐行拆解

仓库给出的标准解答位于 solution.md(运行版源码见 _js.view/solution.js):

function filterRangeInPlace(arr, a, b) { for (let i = 0; i < arr.length; i++) { let val = arr[i]; // remove if outside of the interval if (val < a || val > b) { arr.splice(i, 1); i--; } } } let arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 1, 4); // removed the numbers except from 1 to 4 alert( arr ); // [3, 1]

2.1 判定条件:取反写法

区间保留条件是a ≤ val ≤ b,代码中直接使用其否定形式:

if (val < a || val > b) {

即"小于下界或大于上界"的元素就是要被删除的元素。这里刻意用||连接两个越界方向,而非!(val >= a && val <= b),语义上等价,但先写出"删除条件"会让阅读者更直观地理解循环体的动作。非破坏性版本 2-filter-range/solution.md 则采用保留写法并加括号提升可读性:

return arr.filter(item => (a <= item && item <= b));

两种写法互为镜像,正好对应"删掉越界的"与"留下区间的"两种视角。

2.2 删除机制:splice 而非 delete

删除数组元素时,本章正文(Array methods) 的splice一节特别强调:直接使用delete arr[i]只会把该位置的值置为undefined,数组长度不变,留下空洞;而splice(i, 1)会真正移除该元素,并把后续所有元素前移补齐,数组长度减一。

splice的完整语法为:

arr.splice(start[, deleteCount, elem1, ..., elemN])

它从start索引开始,删除deleteCount个元素(可再插入新元素),并返回被删除元素组成的数组。本解法每次只删除 1 个元素,因此写为arr.splice(i, 1),返回的被删数组此处不使用。

2.3 核心难点:删除后索引回退i--

这是本解法最值得反复咀嚼的一行。正向for循环遍历时,一旦在索引i处执行splice(i, 1),原本位于i+1的元素会整体左移一位,顶替到位置i。如果此时不做处理,for循环的i++会让下一个迭代跳过这个"顶替上来的元素",造成漏检。

因此代码在删除后立刻执行:

i--;

与循环末尾的i++抵消,使下一轮迭代仍停留在原位置,检查刚左移过来的元素。以arr = [5, 3, 8, 1]、区间[1, 4]为例:

迭代i当前数组元素是否越界动作数组变化
10[5, 3, 8, 1]5是(5 > 4)splice(0,1); i--[3, 8, 1]
20[3, 8, 1]3否(1 ≤ 3 ≤ 4)跳过[3, 8, 1]
31[3, 8, 1]8是(8 > 4)splice(1,1); i--[3, 1]
41[3, 1]1否(1 ≤ 1 ≤ 4)跳过[3, 1]

可见正是i--保证了左移元素(如第二次迭代中的 8)不会被漏掉,最终得到[3, 1],与题目示例完全一致。

2.4 为什么函数不写 return

由于数组是引用类型,arr.splice(...)的修改直接作用于调用者传入的同一个数组对象。函数体结束后,外部变量arr已经指向被改写后的数组,因此函数无需、也不应返回新值。若画蛇添足地返回新数组,反而违背题目"only modify the array"的约束,也容易让调用方误以为原数组未被修改。


三、测试用例验证:仓库如何保证正确性

仓库为每个习题都配套了可运行的 Mocha 测试,本习题的测试位于 _js.view/test.js:

describe("filterRangeInPlace", function() { it("returns the filtered values", function() { let arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 2, 5); assert.deepEqual(arr, [5, 3]); }); it("doesn't return anything", function() { assert.isUndefined(filterRangeInPlace([1,2,3], 1, 4)); }); });

两条用例精确对应题目的两个硬性要求:

  1. 原地过滤生效:对[5, 3, 8, 1]调用filterRangeInPlace(arr, 2, 5)后,用assert.deepEqual断言arr变为[5, 3](深比较,确保数组内容与顺序都正确);
  2. 无返回值:assert.isUndefined(...)断言函数调用的返回值为undefined,防止有人写出"返回新数组"的偏离实现。

这两个断言从行为层面完整锁定了"修改原数组 + 不返回值"的契约,是理解本习题意图最直接的佐证。


四、与非原地版本 filterRange 的对比与选型

本习题与 Filter range 互为对照,这里汇总两者差异:

维度filterRange(非原地)filterRangeInPlace(原地)
实现核心arr.filter(item => a <= item && item <= b)循环 +arr.splice(i, 1)+i--
是否修改原数组否是
返回值新数组无(undefined)
适用场景需要保留原始数据做后续对比/回溯数据量大或仅需保留区间内数据,节省内存与拷贝开销
时间复杂度O(n)(新数组构建)O(n·m)(每次 splice 触发元素前移,最坏为 O(n²))

从性能上看,filter版本只需一次线性扫描并拷贝符合条件的元素;而splice每次删除都会让后续元素左移,连续大量删除时会有额外的元素搬运成本。因此原地版本的价值在于空间与引用语义(不产生新数组、调用方持有的引用即最终结果),而非性能优势。当数组中大部分元素都需要删除、且对性能敏感时,更推荐的做法是"先标记、后单次重排",或直接改用filter配合重新赋值arr.length,这些是超出本题范围的工程化延伸。


五、延伸讨论:遍历中删除元素的一般规律

filterRangeInPlace展示的i--技巧,是"边遍历边删除"类问题的通用范式。与之配套的常见替代方案包括:

  • 倒序遍历(从末尾向开头):for (let i = arr.length - 1; i >= 0; i--),删除i位置的元素不影响前面尚未访问的索引,可以省去i--,代码更不易出错;
  • 先收集再批量处理:遍历中只记录待删索引,结束后统一处理,避免频繁触发元素搬移;
  • filter 重赋值:arr = arr.filter(...)或arr.splice(0, arr.length, ...filtered),用函数式风格表达"原地替换"意图。

本教程选择的splice + i--正向遍历,胜在把"数组元素在删除后会左移"这个底层机制直接暴露给读者,是理解数组内存布局的最佳教学案例。相关splice的完整语法、负索引支持与插入/替换能力,均可继续查阅 Array methods 正文 的splice一节,其中还包含delete与splice行为差异的详细演示。


总结

filterRangeInPlace这道习题的价值不在算法复杂度,而在于三点:其一,明确了"闭区间[a, b]内保留、越界删除"的判定条件及其取反写法;其二,演示了splice(i, 1)作为真正的元素删除手段与delete的本质区别;其三,通过i--揭示了正向遍历中删除元素后索引错位的经典陷阱。配合 solution.md、test.js 与 filterRange 对照实现,读者既能获得可运行的完整代码,也能从测试用例中反推题目的行为契约,从而真正掌握"原地修改数组"这一 JavaScript 日常开发中的高频技能。

  • 文档/教程
  • 前端

【免费下载链接】en.javascript.info

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载
上一篇:ElectronBot 3D打印故障排除:翘边、层间分离与堵头解决
下一篇:POCO C++库进程间通信完整指南:跨平台IPC实战教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询