11. 参数处理
- 11.1. 概述
- 11.1.1. 默认参数值
- 11.1.2. 剩余参数
- 11.1.3. 通过解构实现命名参数
- 11.1.4. 展开运算符 (
...
)
- 11.2. 参数处理作为解构
- 11.3. 参数默认值
- 11.3.1. 为什么
undefined
会触发默认值?
- 11.3.2. 在默认值中引用其他参数
- 11.3.3. 在默认值中引用“内部”变量
- 11.4. 剩余参数
- 11.5. 模拟命名参数
- 11.5.1. 命名参数作为描述
- 11.5.2. 可选命名参数
- 11.5.3. 在 JavaScript 中模拟命名参数
- 11.6. 解构在参数处理中的示例
- 11.6.1. forEach() 和解构
- 11.6.2. 转换 Map
- 11.6.3. 处理通过 Promise 返回的数组
- 11.7. 编码风格建议
- 11.7.1. 可选参数
- 11.7.2. 必需参数
- 11.7.3. 强制最大参数个数
- 11.8. 展开运算符 (
...
)
- 11.8.1. 在函数和方法调用中展开
- 11.8.2. 在构造函数中展开
- 11.8.3. 在数组中展开
11.1 概述
ECMAScript 6 中的参数处理功能得到了显著提升。它现在支持参数默认值、剩余参数 (varargs) 和解构。
此外,展开运算符有助于函数/方法/构造函数调用和数组字面量。
11.1.1 默认参数值
默认参数值是通过等号 (=
) 为参数指定的。如果调用者没有为参数提供值,则使用默认值。在以下示例中,y
的默认参数值为 0
11.1.2 剩余参数
如果在参数名称前加上剩余运算符 (...
),则该参数将通过数组接收所有剩余参数
11.1.3 通过解构实现命名参数
如果在参数列表中使用对象模式进行解构,则可以模拟命名参数
A 行中的 = {}
使您可以在不带参数的情况下调用 selectEntries()
。
11.1.4 展开运算符 (...
)
在函数和构造函数调用中,展开运算符将可迭代值转换为参数
在数组字面量中,展开运算符将可迭代值转换为数组元素
11.2 参数处理作为解构
ES6 处理参数的方式等效于通过形式参数解构实际参数。也就是说,以下函数调用
大致等效于
示例 - 以下函数调用
变成
接下来让我们看看具体的功能。
11.3 参数默认值
ECMAScript 6 允许您为参数指定默认值
省略第二个参数会触发默认值
注意 - undefined
也会触发默认值
默认值是按需计算的,仅在实际需要时才计算
11.3.1 为什么 undefined
会触发默认值?
为什么 undefined
应该被解释为缺少参数或缺少对象或数组的一部分,这并不是很明显。这样做的理由是它使您能够委托默认值的定义。让我们看两个例子。
在第一个示例中(来源:Rick Waldron 2012 年 7 月 24 日的 TC39 会议记录),我们不必在 setOptions()
中定义默认值,我们可以将该任务委托给 setLevel()
。
在第二个示例中,square()
不必为 x
定义默认值,它可以将该任务委托给 multiply()
默认值进一步巩固了 undefined
的作用,即表示某事物不存在,而 null
表示空值。
11.3.2 在默认值中引用其他参数
在参数默认值中,您可以引用任何变量,包括其他参数
但是,顺序很重要。参数从左到右声明。在默认值“内部”,如果您访问尚未声明的参数,则会收到 ReferenceError
11.3.3 在默认值中引用“内部”变量
默认值存在于它们自己的作用域中,该作用域位于函数周围的“外部”作用域和函数体的“内部”作用域之间。因此,您无法从默认值访问“内部”变量
如果在前面的示例中没有外部 x
,则默认值 x
将产生 ReferenceError
(如果被触发)。
如果默认值是闭包,则此限制可能是最令人惊讶的
11.4 剩余参数
将剩余运算符 (...
) 放在最后一个形式参数前面意味着它将在数组中接收所有剩余的实际参数。
如果没有剩余参数,则剩余参数将设置为数组
11.4.1 不再使用 arguments
!
剩余参数可以完全替换 JavaScript 臭名昭著的特殊变量 arguments
。它们的优点是始终是数组
11.4.1.1 组合解构和对解构值的访问
arguments
的一个有趣特性是您可以同时拥有普通参数和所有参数的数组
如果将剩余参数与数组解构结合起来,则可以在这种情况下避免使用 arguments
。生成的代码更长,但更清晰
相同的技术适用于命名参数(选项对象)
11.4.1.2 arguments
是可迭代的
arguments
在 ECMAScript 6 中是可迭代的5,这意味着您可以使用 for-of
和展开运算符
11.5 模拟命名参数
在编程语言中调用函数(或方法)时,必须将实际参数(由调用者指定)映射到形式参数(函数定义)。有两种常见的方法可以做到这一点
-
位置参数按位置映射。第一个实际参数映射到第一个形式参数,第二个实际参数映射到第二个形式参数,依此类推
-
命名参数使用名称(标签)来执行映射。形式参数具有标签。在函数调用中,这些标签确定哪个值属于哪个形式参数。只要标记正确,命名实际参数的出现顺序无关紧要。在 JavaScript 中模拟命名参数如下所示。
命名参数有两个主要优点:它们为函数调用中的参数提供描述,并且它们适用于可选参数。我将首先解释这些好处,然后向您展示如何通过对象字面量在 JavaScript 中模拟命名参数。
11.5.1 命名参数作为描述
一旦函数有多个参数,您可能会混淆每个参数的用途。例如,假设您有一个函数 selectEntries()
,它从数据库返回条目。给定函数调用
这三个数字是什么意思?Python 支持命名参数,它们可以很容易地弄清楚发生了什么
11.5.2 可选命名参数
可选位置参数仅在末尾省略时才有效。在其他任何地方,您都必须插入占位符,例如 null
,以便其余参数具有正确的位置。
使用可选命名参数,这不是问题。您可以轻松省略其中任何一个。以下是一些示例
11.5.3 在 JavaScript 中模拟命名参数
与 Python 和许多其他语言不同,JavaScript 本身不支持命名参数。但是有一个相当优雅的模拟:每个实际参数都是对象字面量中的一个属性,其结果作为单个形式参数传递给被调用者。当您使用此技术时,selectEntries()
的调用如下所示。
该函数接收一个具有属性 start
、end
和 step
的对象。您可以省略其中任何一个
在 ECMAScript 5 中,您将按如下方式实现 selectEntries()
在 ECMAScript 6 中,您可以使用解构,如下所示
如果使用零个参数调用 selectEntries()
,则解构将失败,因为您无法将对象模式与 undefined
匹配。这可以通过默认值来解决。在以下代码中,如果缺少第一个参数,则对象模式与 {}
匹配。
您还可以将位置参数与命名参数组合在一起。通常后者排在最后
原则上,JavaScript 引擎可以优化此模式,以便不创建中间对象,因为调用站点上的对象字面量和函数定义中的对象模式都是静态的。
11.6 解构在参数处理中的示例
11.6.1 forEach() 和解构
您可能在 ECMAScript 6 中主要使用 for-of
循环,但数组方法 forEach()
也受益于解构。或者更确切地说,它的回调函数受益于解构。
第一个示例:解构数组中的数组。
第二个示例:解构数组中的对象。
ECMAScript 6 Map 没有 map()
方法(像数组一样)。因此,必须
- 步骤 1:将其转换为
[key,value]
对的数组。
- 步骤 2:对数组执行
map()
。
- 步骤 3:将结果转换回 Map。
如下所示。
11.6.3 处理通过 Promise 返回的数组
工具方法 Promise.all()
的工作原理如下
- 输入:一个 Promise 的可迭代对象。
- 输出:一个 Promise,一旦最后一个输入的 Promise 被兑现,它就会用一个数组被兑现。该数组包含输入 Promise 的兑现值。
解构有助于处理 Promise.all()
结果被兑现的数组
fetch()
是 XMLHttpRequest
的基于 Promise 的版本。它是 Fetch 标准的一部分。
11.7 编码风格提示
本节介绍了一些用于描述性参数定义的技巧。它们很巧妙,但也有缺点:它们增加了视觉混乱,并可能使您的代码更难理解。
11.7.1 可选参数
有些参数没有默认值,但可以省略。在这种情况下,我有时会使用默认值 undefined
来明确表示该参数是可选的。这是多余的,但具有描述性。
11.7.2 必选参数
在 ECMAScript 5 中,您有几种选择来确保提供了必选参数,但它们都相当笨拙
在 ECMAScript 6 中,您可以(滥用)默认参数值来实现更简洁的代码(致谢:Allen Wirfs-Brock 的想法)
交互
11.7.3 强制最大参数个数
本节介绍三种强制执行最大参数个数的方法。示例函数 f
的最大参数个数为 2,如果调用者提供了超过 2 个参数,则应抛出错误。
第一种方法是将所有实际参数收集到形式上的剩余参数 args
中,并检查其长度。
第二种方法依赖于出现在形式上的剩余参数 empty
中的多余实际参数。
第三种方法使用一个哨兵值,如果存在第三个参数,则该值将消失。需要注意的是,如果第三个参数的值为 undefined
,则默认值 OK
也会被触发。
遗憾的是,这些方法中的每一种都会引入明显的视觉和概念混乱。我倾向于建议检查 arguments.length
,但我也希望 arguments
消失。
11.8 展开运算符 (...
)
展开运算符 (...
) 看起来与剩余运算符完全相同,但作用相反
- 剩余运算符:将可迭代对象的剩余项收集到一个数组中,用于 剩余参数 和 解构。
- 展开运算符:将可迭代对象的项转换为函数调用的参数或数组的元素。
11.8.1 展开到函数和方法调用中
Math.max()
是一个很好的例子,用于演示展开运算符如何在方法调用中工作。 Math.max(x1, x2, ···)
返回值最大的参数。它接受任意数量的参数,但不能应用于数组。展开运算符解决了这个问题
与剩余运算符相比,您可以在部分序列中的任何位置使用展开运算符
另一个例子是 JavaScript 没有办法将一个数组的元素破坏性地追加到另一个数组中。但是,数组确实有方法 push(x1, x2, ···)
,它将其所有参数追加到其接收器。以下代码显示了如何使用 push()
将 arr2
的元素追加到 arr1
。
11.8.2 展开到构造函数中
除了函数和方法调用之外,展开运算符也适用于构造函数调用
这在 ECMAScript 5 中很难实现。
11.8.3 展开到数组中
展开运算符也可以在数组字面量中使用
这为您提供了一种连接数组的便捷方法
展开运算符的一个优点是它的操作数可以是任何可迭代值(与不支持迭代的数组方法 concat()
相反)。
11.8.3.1 将可迭代对象或类数组对象转换为数组
展开运算符允许您将任何可迭代值转换为数组
让我们将一个 Set 转换为一个数组
您自己的可迭代对象可以以相同的方式转换为数组
请注意,就像 for-of
循环一样,展开运算符仅适用于可迭代值。所有内置数据结构都是可迭代的:数组、映射和集合。所有类数组 DOM 数据结构也是可迭代的。
如果您遇到不可迭代但类似数组的内容(索引元素加上属性 length
),则可以使用 Array.from()
6 将其转换为数组