ES6 深入 系列文章介绍了 ECMAScript 标准第六版(简称 ES6)中为 JavaScript 编程语言添加的新特性。
今天的内容是关于两个使 JavaScript 函数语法更具表现力的特性:剩余参数和参数默认值。
剩余参数
在创建 API 时,一个常见的需求是 *可变参数函数*,即接受任意数量参数的函数。例如,String.prototype.concat 方法可以接受任意数量的字符串参数。ES6 通过剩余参数提供了编写可变参数函数的新方法。
为了演示,让我们编写一个简单的可变参数函数 containsAll
,该函数检查一个字符串是否包含多个子字符串。例如,containsAll("banana", "b", "nan")
将返回 true
,而 containsAll("banana", "c", "nan")
将返回 false
。
这是实现该函数的传统方式
function containsAll(haystack) {
for (var i = 1; i < arguments.length; i++) {
var needle = arguments[i];
if (haystack.indexOf(needle) === -1) {
return false;
}
}
return true;
}
此实现使用了神奇的 arguments
对象,它是一个类似数组的对象,包含传递给函数的参数。这段代码确实可以完成我们想要的功能,但可读性并不理想。函数参数列表仅包含一个参数 haystack
,因此无法一目了然地知道该函数实际上接受多个参数。此外,我们必须注意从索引 1
而不是 0
开始遍历 arguments
,因为 arguments[0]
对应于 haystack
参数。如果我们想在 haystack
之前或之后添加另一个参数,我们必须记住更新 for 循环。剩余参数解决了这两个问题。以下是使用剩余参数的 ES6 实现 containsAll
的自然方式
function containsAll(haystack, ...needles) {
for (var needle of needles) {
if (haystack.indexOf(needle) === -1) {
return false;
}
}
return true;
}
这个版本的函数与第一个版本的行为相同,但包含了特殊的 ...needles
语法。让我们看看调用此函数的工作原理,以调用 containsAll("banana", "b", "nan")
为例。参数 haystack
像往常一样用第一个传递的参数填充,即 "banana"
。needles
前面的省略号表示它是一个 *剩余参数*。所有其他传递的参数将被放入一个数组并分配给变量 needles
。对于我们示例调用,needles
被设置为 ["b", "nan"]
。然后,函数执行像往常一样继续。(请注意,我们使用了 ES6 的 for-of 循环结构。)
函数中只有最后一个参数可以标记为剩余参数。在调用中,剩余参数之前的参数将像往常一样填充。任何“额外的”参数将被放入一个数组并分配给剩余参数。如果没有额外的参数,剩余参数将只是一个空数组;剩余参数永远不会是 undefined
。
默认参数
通常,函数不需要所有可能的参数都由调用者传递,并且对于未传递的参数,可以使用合理的默认值。JavaScript 一直以来都有一个不灵活的默认参数形式;对于没有传递值的参数,默认为 undefined
。ES6 引入了一种指定任意参数默认值的方法。
下面是一个示例。(反引号表示模板字符串,它们在 上周讨论过。)
function animalSentence(animals2="tigers", animals3="bears") {
return `Lions and ${animals2} and ${animals3}! Oh my!`;
}
对于每个参数,=
后面的部分是一个表达式,指定了如果调用者没有传递该参数,则该参数的默认值。因此,animalSentence()
返回 "Lions and tigers and bears! Oh my!"
,animalSentence("elephants")
返回 "Lions and elephants and bears! Oh my!"
,而 animalSentence("elephants", "whales")
返回 "Lions and elephants and whales! Oh my!"
。
与默认参数相关联的几个微妙之处
- 与 Python 不同,**默认值表达式在函数调用时从左到右进行评估**。这也意味着默认表达式可以使用先前填充的参数的值。例如,我们可以将我们的动物句子函数变得更高级,如下所示
function animalSentenceFancy(animals2="tigers", animals3=(animals2 == "bears") ? "sealions" : "bears") { return `Lions and ${animals2} and ${animals3}! Oh my!`; }
然后,
animalSentenceFancy("bears")
返回"Lions and bears and sealions. Oh my!"
。 - 传递
undefined
被认为等同于不传递任何内容。因此,animalSentence(undefined, "unicorns")
返回"Lions and tigers and unicorns! Oh my!"
。 - 没有默认值的参数隐式默认为 undefined,因此
function myFunc(a=42, b) {...}
是允许的,等同于
function myFunc(a=42, b=undefined) {...}
关闭 arguments
我们现在已经看到,剩余参数和默认值可以替代 arguments
对象的使用,而移除 arguments
通常会使代码更易于阅读。除了影响可读性之外,arguments
对象的神奇之处还会臭名昭著地导致 优化 JavaScript VM 的头痛问题.
希望剩余参数和默认值可以完全取代 arguments
。作为迈向这一目标的第一步,使用剩余参数或默认值的函数被禁止使用 arguments
对象。对 arguments
的支持不会很快消失,即使永远不会消失,但现在最好在可能的情况下使用剩余参数和默认值来避免使用 arguments
。
浏览器支持
Firefox 从 15 版本开始支持剩余参数和默认值。
不幸的是,其他已发布的浏览器尚未支持剩余参数或默认值。V8 最近 添加了对剩余参数的实验性支持,并且有一个开放的 V8 问题用于实现默认值。JSC 也有关于 剩余参数 和 默认值 的开放问题。
Babel 和 Traceur 编译器都支持默认参数,因此您可以从今天开始使用它们。
结论
虽然在技术上不允许任何新的行为,但剩余参数和参数默认值可以使某些 JavaScript 函数声明更具表现力和可读性。祝您调用愉快!
注意:感谢 Benjamin Peterson 在 Firefox 中实现了这些功能,感谢他对该项目的所有贡献,当然还要感谢本周的帖子。
下周,我们将介绍另一个简单、优雅、实用、日常的 ES6 功能。它利用您已经用来编写数组和对象的熟悉语法,并将其颠倒过来,产生了一种新的、简洁的方法来 *分解数组和对象*。这意味着什么?为什么要分解对象?下周四加入我们,了解 Mozilla 工程师 Nick Fitzgerald 对 ES6 解构的深入介绍。
Jason Orendorff
ES6 深入 编辑
9 条评论