news 2026/3/24 21:23:24

rest参数与数组操作:从零实现示例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
rest参数与数组操作:从零实现示例

用 rest 参数和数组方法写出更聪明的 JavaScript

你有没有写过这样的函数:明明只想加几个数字,却得先处理arguments?或者想过滤一堆输入,结果被类数组对象折腾得够呛?

function sum() { // 啊!又来了…… var args = Array.prototype.slice.call(arguments); return args.reduce(function(a, b) { => a + b; }, 0); }

这段代码不陌生吧?在 ES6 出现之前,这是标准操作。但今天,我们有更优雅的方式——rest 参数...args)搭配现代数组方法,彻底告别繁琐的参数处理。


从 arguments 到 rest:一次参数处理的进化

JavaScript 的函数一直支持“传多不管”,可问题是,传统方式拿到的是一个叫arguments的“伪数组”。它长得像数组,能用下标访问,但不能直接调用.map().filter()这些好用的方法。

function badExample() { console.log(Array.isArray(arguments)); // false arguments.map(x => x * 2); // TypeError: arguments.map is not a function }

ES6 引入的rest 参数,就是来解决这个问题的。三个点...看似简单,实则改变了我们写函数的方式:

const sum = (...numbers) => numbers.reduce((a, b) => a + b, 0); sum(1, 2, 3, 4); // 10

就这么一行,干净利落。numbers是真正的数组,你想怎么链式调用都行。

rest 参数的三条铁律

  1. 必须在最后
    js function good(a, b, ...rest) { } // ✅ OK function bad(...rest, a, b) { } // ❌ SyntaxError

  2. 只能有一个
    同一个函数里别想着搞两个 rest,语法不允许。

  3. 名字自己定
    ...args...nums...inputs都可以,选个语义清晰的名字比什么都重要。


数组方法加持:让数据自己“流动”起来

有了真正的数组,接下来就该轮到.map.filter.reduce登场了。它们不是新东西,但在 rest 的配合下,威力翻倍。

示例一:邮箱提取器

假设你要从一堆输入中找出合法邮箱:

function extractEmails(...inputs) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return inputs .filter(input => typeof input === 'string') // 只留字符串 .filter(str => emailRegex.test(str)); // 再筛出合规格式 } extractEmails('hello', 'user@example.com', null, 'invalid@.com'); // ['user@example.com']

注意这里的链式调用。每一步都在描述“我们要什么”,而不是“怎么一步步找”。这就是声明式编程的魅力。

示例二:构建高阶校验函数

再来看个更有意思的场景:动态字段验证。

function requiredFields(...fields) { return function(obj) { const missing = fields.filter(field => !(field in obj)); if (missing.length) { throw new Error(`缺少必要字段:${missing.join(', ')}`); } return true; }; } const checkUser = requiredFields('id', 'name', 'email'); checkUser({ id: 1, name: 'Alice' }); // 报错:缺少 email

这个模式在表单验证、API 接口检查中非常实用。你把“规则”提前定义好,运行时只需传入数据即可。


实战技巧:那些文档不会告诉你的事

技巧 1:类型校验也能很灵活

有时候你不只想收参数,还想确保它们类型正确。结合 rest 和类型检查,可以写出健壮的工具函数:

function processNumbers(...nums) { if (!nums.every(n => typeof n === 'number')) { throw new TypeError('所有参数必须是数字'); } return nums.map(n => n ** 2); } processNumbers(1, 2, 3); // [1, 4, 9]

如果你经常做这类判断,甚至可以抽象成通用辅助函数:

const isTypeOf = (type, ...values) => values.every(v => typeof v === type); isTypeOf('string', 'a', 'b', 'c'); // true

技巧 2:日志函数的现代化写法

看看这个带时间戳的日志函数:

function log(level, ...messages) { const time = new Date().toISOString(); const output = messages.map(msg => typeof msg === 'object' ? JSON.stringify(msg) : String(msg) ).join(' '); console[level]?.(`${time} [${level.toUpperCase()}] ${output}`); } log('info', '用户登录', { userId: 123 }); // 输出类似:2025-04-05T10:00:00.000Z [INFO] 用户登录 {"userId":123}

这里的关键在于:
- 第一个参数控制日志级别;
- 剩下的全交给 rest 收集;
- 自动识别对象并序列化,避免[object Object]的尴尬。


常见坑点与避坑指南

❌ 不要滥用 rest

不是每个函数都需要...args。如果参数数量固定,比如(a, b, c),硬加上 rest 反而会让调用者困惑。

✅ 正确使用场景:
- 工具函数(如Math.max那种)
- 事件监听器接收多个回调
- 配置合并函数
- 日志、调试等辅助功能

⚠️ 性能要考虑

虽然 rest 很方便,但如果函数被高频调用且传入大量参数,会生成大数组,增加内存压力。

比如这种写法就要小心:

elements.forEach(el => render(el, ...hugePropsArray));

频繁展开可能影响性能,必要时考虑传引用。

📝 文档别偷懒

用 JSDoc 注明 rest 参数的含义,对团队协作至关重要:

/** * 计算数值平均值 * @param {...number} numbers - 任意数量的数字 * @returns {number} 平均值,无参数时返回 0 */ function average(...numbers) { return numbers.length ? numbers.reduce((a, b) => a + b) / numbers.length : 0; }

IDE 能自动识别这些注释,提升开发体验。


rest vs spread:别再傻傻分不清

很多人混淆这两个操作符,其实记住一句话就行:

rest 是“收进来”,spread 是“打出去”

// 收:把参数收集为数组 function gather(...args) { console.log(args); // [1, 2, 3] } // 打:把数组展开为参数 const arr = [1, 2, 3]; Math.max(...arr); // 相当于 Math.max(1, 2, 3)

它们是一对镜像操作,在函数定义和调用之间架起桥梁。


写在最后

rest 参数看似只是一个语法糖,但它背后代表的是 JavaScript 编程范式的转变:从“手动管理参数”到“让数据自然流动”。

当你开始习惯用(...items) => items.filter(...).map(...)这样的链条去思考问题时,你就已经迈入了现代 JS 开发的大门。

尤其是在 React、Node.js 工具链、配置系统中,这种组合技随处可见。掌握它,不只是为了少写几行代码,更是为了写出更容易理解、测试和维护的逻辑。

如果你现在还在用Array.prototype.slice.call(arguments),不妨停下来,试试这行:

js const fn = (...args) => { /* your logic */ };

你会发现,代码一下子清爽了。

你平时在哪种场景下最喜欢用 rest 参数?欢迎在评论区分享你的实战经验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/22 15:59:34

ResNet18实战:电商平台商品自动标注系统

ResNet18实战:电商平台商品自动标注系统 1. 引言:通用物体识别的工程价值 在电商场景中,海量商品图像的自动化处理是提升运营效率的关键环节。传统的人工标注方式成本高、速度慢,难以应对每日新增的数十万级商品图片。而基于深度…

作者头像 李华
网站建设 2026/3/24 12:09:23

ResNet18技术解析:模型架构与训练细节

ResNet18技术解析:模型架构与训练细节 1. 引言:通用物体识别中的ResNet18 在计算机视觉领域,图像分类是基础且关键的任务之一。随着深度学习的发展,卷积神经网络(CNN)在该任务中取得了突破性进展。其中&a…

作者头像 李华
网站建设 2026/3/23 9:31:42

D触发器电路图入门必看:74HC74典型应用电路

从零开始搞懂D触发器:74HC74不只是教科书里的芯片你有没有遇到过这种情况——按键明明只按了一次,单片机却误判成好几次?或者外部信号来得“不守时”,导致系统状态混乱?又或者想用5个IO口控制16颗LED,发现引…

作者头像 李华
网站建设 2026/3/24 0:14:46

ResNet18教程:多模型集成提升准确率

ResNet18教程:多模型集成提升准确率 1. 引言:通用物体识别中的ResNet-18价值 在计算机视觉领域,通用物体识别是构建智能系统的基础能力之一。无论是自动驾驶感知环境、智能家居理解用户场景,还是内容平台自动打标,都…

作者头像 李华
网站建设 2026/3/20 6:53:36

清除Chrome、Edge等浏览器的临时文件和历史记录

引言介绍C盘空间不足的常见问题及其影响强调定期清理的重要性清理临时文件使用Windows内置的磁盘清理工具手动删除临时文件夹(如%temp%、C:\Windows\Temp)卸载无用程序通过控制面板或设置卸载不常用的软件使用第三方工具(如Revo Uninstaller&…

作者头像 李华
网站建设 2026/3/24 12:24:41

ResNet18部署教程:Docker容器化方案详细步骤

ResNet18部署教程:Docker容器化方案详细步骤 1. 引言 1.1 通用物体识别的工程需求 在AI应用落地过程中,通用物体识别是智能监控、内容审核、图像检索等场景的核心能力。尽管深度学习模型日益复杂,但在实际生产中,稳定性、轻量化…

作者头像 李华