本文深入解析JavaScript ES6引入的Rest参数和参数默认值特性。通过对比传统arguments对象,展示如何利用...语法接收可变参数及为形参指定默认表达式。文章涵盖语法细节、计算顺序、兼容性现状及最佳实践,帮助开发者提升代码可读性并逐步弃用arguments。
Rest 参数详解
Rest 参数
在开发中,经常需要创建能够接收任意数量参数的函数,这类函数被称为可变参数函数。以 String.prototype.concat 为例,它可以接受任意数量的字符串作为输入。ES6 引入了 Rest 参数机制,为构建此类函数提供了更简洁的新方案。
接下来实现一个名为 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;
}
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,读者难以直观看出该函数需要接收多个参数。此外,遍历 arguments 时必须注意起始索引为 1 而非 0,因为 arguments[0] 对应的是形参 haystack。若需在 haystack 前后增加其他参数,还需手动调整内部循环逻辑。Rest 参数有效解决了这些痛点,其实现方式如下:
function containsAll(haystack, ...needles) {
for (var needle of needles) {
if (haystack.indexOf(needle) === -1) {
return false;
}
}
return true;
}
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 前的省略号表明它是 Rest 参数,所有剩余的实参会被封装成一个数组并赋值给 needles 变量。在此调用中,needles 的值为 ["b", "nan"]。随后,函数主体按正常逻辑执行。
Rest 参数必须位于函数参数列表的末尾。调用函数时,Rest 参数之前的参数按常规方式填充,而剩余的所有参数会被放入一个数组中作为 Rest 参数的值。即使没有剩余参数,Rest 参数的值也是一个空数组 [],绝不会是 undefined。
参数默认值机制
参数的默认值
通常情况下,调用函数时无需传递所有可能的参数,未传递的参数应具备合理的默认值。在 ES6 之前,JavaScript 对未传递的参数默认赋予 undefined 值。ES6 引入了新语法,允许为任意参数指定默认值。
参考以下示例:
function animalSentence(animals2="tigers", animals3="bears") {
return `Lions and ${animals2} and ${animals3}! Oh my!`;
}
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 不同,JavaScript 中参数默认值的表达式是在函数调用时从左到右计算的。这意味着表达式可以引用前面已被赋值的参数。例如,我们可以将上述函数改写得更复杂:
function animalSentenceFancy(animals2="tigers",
animals3=(animals2 == "bears") ? "sealions" : "bears")
{
return `Lions and ${animals2} and ${animals3}! Oh my!`;
}
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) {...}
等同于
代码如下:
function myFunc(a=42, b=undefined) {...}
function myFunc(a=42, b=undefined) {...}
弃用 arguments 对象
抛弃 arguments
通过结合使用 Rest 参数和参数默认值,我们可以完全摒弃 arguments 对象,从而显著提升代码的可读性。此外,arguments 对象的存在也增加了 JavaScript 引擎优化的难度。
期望上述两个新特性能逐步取代 arguments。作为过渡策略,在使用 Rest 参数或参数默认值时,请避免混用 arguments 对象。即便 arguments 对象短期内不会被移除或永远存在,在已使用新特性的代码中避免使用它也是最佳实践。
浏览器兼容性与总结
兼容性
Firefox 15 及以上版本已支持这两个新特性。然而,其他浏览器在当时尚未提供支持。近期,V8 引擎的实验环境添加了对 Rest 参数的支持,但参数默认值仍存在相关 Issue,JSC 引擎也对这两个特性提出了一些 Issue。
Babel 和 Traceur 这两个编译器均已支持参数默认值的转换,因此开发者可以安全地使用这些特性。
结论
尽管从技术层面看,这两个新特性并未为函数引入全新的行为逻辑,但它们使函数声明更具表现力且易于阅读。
以上就是本文的全部内容,希望对大家的学习有所帮助。