ES6 系列:你不知道的 Rest 参数与 Spread 语法细节

简介: ES6 系列:你不知道的 Rest 参数与 Spread 语法细节

640.png

Rest 参数与 Spread 语法


在 JavaScript 中,很多内建函数都支持传入任意数量的参数。

例如:


  • Math.max(arg1, arg2, ..., argN) —— 返回入参中的最大值。
  • Object.assign(dest, src1, ..., srcN) —— 依次将属性从 src1..N 复制到 dest
  • ……等。


在本文中,我们将学习如何编程实现支持函数可传入任意数量的参数。以及,如何将数组作为参数传递给这类函数。


Rest 参数 ...


在 JavaScript 中,无论函数是如何定义的,你都可以使用任意数量的参数调用函数。

例如:


function sum(a, b) {
  return a + b;
}
alert( sum(1, 2, 3, 4, 5) );


虽然这里不会因为传入“过多”的参数而报错。但是当然,在结果中只有前两个参数被计算进去了。


Rest 参数可以通过使用三个点 ... 并在后面跟着包含剩余参数的数组名称,来将它们包含在函数定义中。这些点的字面意思是“将剩余参数收集到一个数组中”。

例如,我们需要把所有的参数都放到数组 args 中:


function sumAll(...args) { // 数组名为 args
  let sum = 0;
  for (let arg of args) sum += arg;
  return sum;
}
alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6


我们也可以选择获取第一个参数作为变量,并将剩余的参数收集起来。

下面的例子把前两个参数定义为变量,并把剩余的参数收集到 titles 数组中:


function showName(firstName, lastName, ...titles) {
  alert( firstName + ' ' + lastName ); // Julius Caesar
  // 剩余的参数被放入 titles 数组中
  // i.e. titles = ["Consul", "Imperator"]
  alert( titles[0] ); // Consul
  alert( titles[1] ); // Imperator
  alert( titles.length ); // 2
}
showName("Julius", "Caesar", "Consul", "Imperator");

Rest 参数必须放到参数列表的末尾

Rest 参数会收集剩余的所有参数,因此下面这种用法没有意义,并且会导致错误:

function f(arg1, ...rest, arg2) { // arg2 在 ...rest 后面?!
  // error
}

...rest 必须处在最后。


"arguments" 变量


有一个名为 arguments 的特殊的类数组对象,该对象按参数索引包含所有参数。

例如:


function showName() {
  alert( arguments.length );
  alert( arguments[0] );
  alert( arguments[1] );
  // 它是可遍历的
  // for(let arg of arguments) alert(arg);
}
// 依次显示:2,Julius,Caesar
showName("Julius", "Caesar");
// 依次显示:1,Ilya,undefined(没有第二个参数)
showName("Ilya");


在过去,JavaScript 中没有 rest 参数,而使用 arguments 是获取函数所有参数的唯一方法。现在它仍然有效,我们可以在一些老代码里找到它。


但缺点是,尽管 arguments 是一个类数组,也是可迭代对象,但它终究不是数组。它不支持数组方法,因此我们不能调用 arguments.map(...) 等方法。


此外,它始终包含所有参数,我们不能像使用 rest 参数那样只截取入参的一部分。

因此,当我们需要这些功能时,最好使用 rest 参数。


箭头函数是没有 "arguments"

如果我们在箭头函数中访问 arguments,访问到的 arguments 并不属于箭头函数,而是属于箭头函数外部的“普通”函数。

举个例子:


function f() {
  let showArg = () => alert(arguments[0]);
  showArg();
}
f(1); // 1
我们已经知道,箭头函数没有自身的 `this`。现在我们知道了它们也没有特殊的 `arguments` 对象。


Spread 语法


我们刚刚看到了如何从参数列表中获取数组。

不过有时候我们也需要做与之相反的事儿。

例如,内建函数 Math.max[1] 会返回参数中最大的值:


alert( Math.max(3, 5, 1) ); // 5


假如我们有一个数组 [3, 5, 1],我们该如何用它调用 Math.max 呢?

直接把数组“原样”传入是不会奏效的,因为 Math.max 希望你传入一个列表形式的数值型参数,而不是一个数组:


let arr = [3, 5, 1];
alert( Math.max(arr) ); // NaN


毫无疑问,我们不可能手动地去一一设置参数 Math.max(arg[0], arg[1], arg[2]),因为我们不确定这儿有多少个。在脚本执行时,可能参数数组中有很多个元素,也可能一个都没有。并且这样设置的代码也很丑。

Spread 语法 来帮助你了!它看起来和 rest 参数很像,也使用 ...,但是二者的用途完全相反。

当在函数调用中使用 ...arr 时,它会把可迭代对象 arr “展开”到参数列表中。

Math.max 为例:


let arr = [3, 5, 1];
alert( Math.max(...arr) ); // 5(spread 语法把数组转换为参数列表)


我们还可以通过这种方式传递多个可迭代对象:


let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];
alert( Math.max(...arr1, ...arr2) ); // 8


我们甚至还可以将 spread 语法与常规值结合使用:


let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];
alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25


并且,我们还可以使用 spread 语法来合并数组:


let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
let merged = [0, ...arr, 2, ...arr2];
alert(merged); // 0,3,5,1,2,8,9,15(0,然后是 arr,然后是 2,然后是 arr2)


在上面的示例中,我们使用数组展示了 spread 语法,其实任何可迭代对象都可以。

例如,在这儿我们使用 spread 语法将字符串转换为字符数组:


let str = "Hello";
alert( [...str] ); // H,e,l,l,o


Spread 语法内部使用了迭代器来收集元素,与 for..of 的方式相同。

因此,对于一个字符串,for..of 会逐个返回该字符串中的字符,...str 也同理会得到 "H","e","l","l","o" 这样的结果。随后,字符列表被传递给数组初始化器 [...str]

对于这个特定任务,我们还可以使用 Array.from 来实现,因为该方法会将一个可迭代对象(如字符串)转换为数组:


let str = "Hello";
// Array.from 将可迭代对象转换为数组
alert( Array.from(str) ); // H,e,l,l,o


运行结果与 [...str] 相同。

不过 Array.from(obj)[...obj] 存在一个细微的差别:

  • Array.from 适用于类数组对象也适用于可迭代对象。
  • Spread 语法只适用于可迭代对象。

因此,对于将一些“东西”转换为数组的任务,Array.from 往往更通用。


获取一个 array/object 的副本


还记得我们 之前讲过的[2]Object.assign() 吗?


使用 spread 语法也可以做同样的事情。


let arr = [1, 2, 3];
let arrCopy = [...arr]; // 将数组 spread 到参数列表中
                        // 然后将结果放到一个新数组
// 两个数组中的内容相同吗?
alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true
// 两个数组相等吗?
alert(arr === arrCopy); // false(它们的引用是不同的)
// 修改我们初始的数组不会修改副本:
arr.push(4);
alert(arr); // 1, 2, 3, 4
alert(arrCopy); // 1, 2, 3


并且,也可以通过相同的方式来复制一个对象:


let obj = { a: 1, b: 2, c: 3 };
let objCopy = { ...obj }; // 将对象 spread 到参数列表中
                          // 然后将结果返回到一个新对象
// 两个对象中的内容相同吗?
alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true
// 两个对象相等吗?
alert(obj === objCopy); // false (not same reference)
// 修改我们初始的对象不会修改副本:
obj.d = 4;
alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4}
alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}


这种方式比使用 let arrCopy = Object.assign([], arr); 来复制数组,或使用 let objCopy = Object.assign({}, obj); 来复制对象写起来要短得多。因此,只要情况允许,我们更喜欢使用它。


总结


当我们在代码中看到 "..." 时,它要么是 rest 参数,要么就是 spread 语法。

有一个简单的方法可以区分它们:


  • ... 出现在函数参数列表的最后,那么它就是 rest 参数,它会把参数列表中剩余的参数收集到一个数组中。
  • ... 出现在函数调用或类似的表达式中,那它就是 spread 语法,它会把一个数组展开为列表。

使用场景:

  • Rest 参数用于创建可接受任意数量参数的函数。
  • Spread 语法用于将数组传递给通常需要含有许多参数的列表的函数。


它们俩的出现帮助我们轻松地在列表和参数数组之间来回转换。


“旧式”的 arguments(类数组且可迭代的对象)也依然能够帮助我们获取函数调用中的所有参数。


目录
相关文章
|
负载均衡 网络协议 安全
slb选择监听协议和端口
阿里云SLB中,监听协议(TCP、HTTP、HTTPS)与端口(80、443等)决定客户端请求的处理方式。TCP适用于纯TCP或自处理HTTP的场景,HTTP用于智能调度Web服务,HTTPS提供安全的HTTP传输。监听端口通常匹配应用标准,如80 for HTTP,443 for HTTPS。配置时,可考虑HTTPS重定向和传递`X-Forwarded-Proto`头以识别请求来源。选择应基于业务需求和安全考虑。
774 3
|
Linux
`grep`命令搜索当前目录及其子目录下的所有文件
`grep`命令搜索当前目录及其子目录下的所有文件
5318 1
|
监控 IDE 开发工具
【esp32c3配置arduino IDE教程】
设计用户操作界面,该设备具备简单易用的操作界面,外加显示屏SSD1306和旋转编码器进行显示和控制,用户后期可进行二次开发WiFi或蓝牙连接电脑或手机监控。
2732 0
|
存储 算法 C++
【C++STL基础入门】vector向量基础使用
【C++STL基础入门】vector向量基础使用
620 1
|
机器学习/深度学习 人工智能 算法
小白教程-阿里云快速搭建Stable-Diffusion WebUI环境+免费试用
Stable-Diffusion 是目前热门的AIGC图像生成方案,通过开源与社区共享模型的方式,成为AI艺术与创意产业的重要工具。本文介绍通过阿里云快速搭建SD WebUI的服务,并有免费试用权益,适合新手入门。通过详细步骤指导,帮助读者轻松上手,享受创作乐趣。
2246 0
|
计算机视觉
ECCV 2024:新梦幻场景生成方法,高质量、视角一致、可编辑3D场景
【10月更文挑战第27天】DreamScene是一种新型的文本到3D场景生成框架,基于3D高斯模型。它通过形成模式采样(FPS)和渐进式三阶段相机采样策略,生成高质量、一致性和可编辑的3D场景。DreamScene在游戏、电影和建筑等行业具有巨大应用潜力,尽管在处理复杂场景时仍存在一些局限性。论文地址:https://arxiv.org/abs/2404.03575
256 1
|
Web App开发 机器人
小白一学就会小红书全自动写文发文机器人-爆文仿写笔记(二) -附提示词
小白一学就会小红书全自动写文发文机器人-爆文仿写笔记(二) -附提示词
618 1
|
消息中间件 运维 监控
基于SaaS云部署、云计算的区域医院云HIS系统源码(运维管理+运营管理+综合监管)
医院云his系统采用主流成熟技术开发,B/S架构,软件结构简洁、代码规范易阅读,SaaS应用,全浏览器访问,前后端分离,多服务协同,服务可拆分,功能易扩展。多医院统一登录患者主索引建立、主数据管理,统一对外接口管理。
310 1
|
Web App开发 监控 应用服务中间件
datax-web修改web端口号
datax-web修改web端口号
1075 4