我最近开始更多地研究 JavaScript 中的新内容,追赶 ES6(即 ES2015 及更高版本)中包含的许多新功能和语法改进。
您可能听说过并开始使用通常的东西:箭头函数、let 和 const、剩余和扩展运算符等等。但是,一个引起我注意的功能是**函数中的默认参数**,现在它是 ES6+ 的正式功能。 这意味着您的函数即使在函数调用不包含参数时也可以使用默认值初始化参数。
此功能本身在最简单的形式中非常简单,但有一些细微之处和需要注意的地方,我将在本文中使用一些代码示例和演示来阐明这些地方。
ES5 及更早版本中的默认参数
自动为未声明参数提供默认值的函数可以作为程序的有益保障,这并非新鲜事。
在 ES6 之前,您可能见过或使用过类似这样的模式
function getInfo (name, year, color) {
year = (typeof year !== 'undefined') ? year : 2018;
color = (typeof color !== 'undefined') ? color : 'Blue';
// remainder of the function...
}
在这种情况下,getInfo()
函数只有一个必填参数:name
。year
和 color
参数是可选的,因此如果在调用 getInfo()
时没有提供它们作为参数,它们将被分配默认值
getInfo('Chevy', 1957, 'Green');
getInfo('Benz', 1965); // default for color is "Blue"
getInfo('Honda'); // defaults are 2018 and "Blue"
如果没有这种检查和保障措施,任何未初始化的参数将默认为 undefined
值,这通常不希望出现。
您也可以使用真值/假值模式来检查没有值的参数
function getInfo (name, year, color) {
year = year || 2018;
color = color || 'Blue';
// remainder of the function...
}
但这在某些情况下可能会导致问题。在上面的示例中,如果您为年份传入 0
的值,则默认的 2018 将覆盖它,因为 0
被评估为假值。在此特定示例中,您不太可能对此感到担忧,但有许多情况是您的应用程序可能希望将 0 的值作为有效数字而不是假值来接受。
当然,即使使用 typeof
模式,您可能也必须进行进一步的检查才能获得真正万无一失的解决方案。例如,您可能希望将一个可选的 回调函数 作为参数。在这种情况下,仅针对 undefined
进行检查是不够的。您还必须检查传入的值是否是一个有效的函数。
所以,这只是 ES6 之前我们如何处理默认参数的总结。让我们看看一个更好的方法。
ES6 中的默认参数
如果您的应用程序出于遗留原因或由于浏览器支持而需要使用 ES6 之前的功能,那么您可能必须执行类似于我上面描述的操作。但 ES6 使此操作变得更加容易。以下是 ES6 及更高版本中定义默认参数值的方法
function getInfo (name, year = 2018, color = 'blue') {
// function body here...
}
就这么简单。
如果将 year
和 color
值传递到函数调用中,则作为参数传入的值将取代在函数定义中定义的参数。这与 ES5 模式完全相同,但没有那么多额外的代码。更容易维护,更容易阅读。
此功能可用于函数头部中的任何参数,因此您可以为第一个参数设置一个默认值,以及两个其他没有默认值的预期值
function getInfo (name = 'Pat', year, color) {
// function body here...
}
处理省略的值
请注意,在像上面这样的情况下,如果您想省略可选的 name
参数(因此使用默认值)同时包含 year
和 color
,则必须将 undefined
作为占位符传递给第一个参数
getInfo(undefined, 1995, 'Orange');
如果您没有这样做,那么从逻辑上讲,第一个值将始终被假定为 name
。
如果要省略 year
参数(第二个参数)同时包含其他两个参数(假设第二个参数是可选的),则同样适用
getInfo('Charlie', undefined, 'Pink');
我还应该注意以下内容可能会产生意外的结果
function getInfo (name, year = 1965, color = 'blue') {
console.log(year); // null
}
getInfo('Frankie', null, 'Purple');
在这种情况下,我将第二个参数传递为 null
,这可能会让一些人认为函数内部的 year
值应该为 1965
,即默认值。但这不会发生,因为 null
被认为是一个有效的值。这是有道理的,因为 根据规范,null
被 JavaScript 引擎视为对对象值的故意缺失,而 undefined
被视为偶然发生的事件(例如,当函数没有返回值时,它会返回 undefined
)。
因此,确保在您希望使用默认值时使用 undefined
而不是 null
。当然,可能有些情况下您希望使用 null
,然后在函数体内处理 null
值,但您应该熟悉这种区别。
arguments
对象
默认参数值和 这里值得一提的另一点与 arguments
对象有关。arguments
对象是一个类似数组的对象,可在函数体内部访问,它表示传递给函数的参数。
在非严格模式下,arguments
对象会反映在函数体内部对参数值所做的任何更改。例如
function getInfo (name, year, color) {
console.log(arguments);
/*
[object Arguments] {
0: "Frankie",
1: 1987,
2: "Red"
}
*/
name = 'Jimmie';
year = 1995;
color = 'Orange';
console.log(arguments);
/*
[object Arguments] {
0: "Jimmie",
1: 1995,
2: "Orange"
}
*/
}
getInfo('Frankie', 1987, 'Red');
请注意,在上面的示例中,如果我更改函数参数的值,这些更改会反映在 arguments
对象中。此功能被认为弊大于利,因此在严格模式下,行为有所不同
function getInfo (name, year, color) {
'use strict';
name = 'Jimmie';
year = 1995;
color = 'Orange';
console.log(arguments);
/*
[object Arguments] {
0: "Frankie",
1: 1987,
2: "Red"
}
*/
}
getInfo('Frankie', 1987, 'Red');
如演示所示,在严格模式下,arguments
对象保留参数的原始值。
这将我们带到了默认参数的使用。当使用默认参数功能时,arguments
对象的行为如何?请查看以下代码
function getInfo (name, year = 1992, color = 'Blue') {
console.log(arguments.length); // 1
console.log(year, color);
// 1992
// "Blue"
year = 1995;
color = 'Orange';
console.log(arguments.length); // Still 1
console.log(arguments);
/*
[object Arguments] {
0: "Frankie"
}
*/
console.log(year, color);
// 1995
// "Orange"
}
getInfo('Frankie');
此示例中需要注意几件事。
首先,包含默认参数不会更改 arguments
对象。因此,在这种情况下,如果我在函数调用中只传递一个参数,arguments
对象将只包含一个项目,即使可选参数存在默认参数。
其次,当存在默认参数时,arguments
对象在严格模式和非严格模式下的行为始终相同。上面的示例是在非严格模式下,通常允许修改 arguments
对象。但这不会发生。如您所见,修改值后 arguments
的长度保持不变。此外,当记录对象本身时,只有 name
值存在。
表达式作为默认参数
默认参数功能不仅限于静态值,还可以包含要计算以确定默认值的表达式。以下是一个演示一些可能性的示例
function getAmount() {
return 100;
}
function getInfo (name, amount = getAmount(), color = name) {
console.log(name, amount, color)
}
getInfo('Scarlet');
// "Scarlet"
// 100
// "Scarlet"
getInfo('Scarlet', 200);
// "Scarlet"
// 200
// "Scarlet"
getInfo('Scarlet', 200, 'Pink');
// "Scarlet"
// 200
// "Pink"
上面代码中需要注意几件事。首先,我允许在函数调用中不包含第二个参数时,通过 getAmount()
函数对其进行计算。只有在不传入第二个参数时才会调用此函数。这在第二次 getInfo()
调用和随后的日志中很明显。
下一个关键点是,我可以使用前一个参数作为另一个参数的默认值。我不确定这有多有用,但知道它可能存在是有好处的。如您在上面的代码中所见,getInfo()
函数将第三个参数 (color
) 设置为等于第一个参数的值 (name
),如果未包含第三个参数。
当然,由于可以使用函数来确定默认参数,因此您也可以将现有参数传递给用作后续参数的函数,如以下示例所示
function getFullPrice(price) {
return (price * 1.13);
}
function getValue (price, pricePlusTax = getFullPrice(price)) {
console.log(price.toFixed(2), pricePlusTax.toFixed(2))
}
getValue(25);
// "25.00"
// "28.25"
getValue(25, 30);
// "25.00"
// "30.00"
在上面的示例中,我在 getFullPrice()
函数中进行了基本税款计算。当调用此函数时,它会将现有的 price
参数用作 pricePlusTax
计算的一部分。如前所述,如果将第二个参数传递给 getValue()
(如第二次 getValue()
调用中所示),则不会调用 getFullPrice()
函数。
关于上述内容需要牢记两点。首先,默认参数表达式中的函数调用需要包含括号,否则您将收到一个函数引用,而不是函数调用的计算结果。
其次,您只能用默认参数引用之前的参数。换句话说,您不能将第二个参数作为参数引用来确定第一个参数的默认值
// this won't work
function getValue (pricePlusTax = getFullPrice(price), price) {
console.log(price.toFixed(2), pricePlusTax.toFixed(2))
}
getValue(25); // throws an error
同样,正如您所预期的那样,您不能从函数参数访问函数体内部定义的变量。
结论
这篇文章涵盖了在 ES6 及更高版本中使用函数默认参数的几乎所有内容。这个功能本身在最简单的形式下非常容易使用,但正如我在这篇文章中讨论的,有很多细节值得理解。
如果您想了解更多关于此主题的信息,以下是一些资料来源。
- 理解 ECMAScript 6 由 Nicholas Zakas 撰写。这是本文的主要资料来源。Nicholas 绝对是我最喜欢的 JavaScript 作者。
- Arguments 对象 在 MDN 上
- 默认参数 在 MDN 上
Chrome 实际上在它成为任何标准的一部分之前就支持它。我在不知道它是 Chrome 特定的情况下使用了这个功能,因为它看起来像每种语言都应该有的东西,而且它似乎可以正常工作。然后我发现 IE 不起作用,我花了很长时间才弄清楚,直到一位同事指出我使用的功能在技术上不是有效的 JavaScript,尽管 Chrome 使它可以正常工作。很高兴看到它成为官方的。函数开头的 typeof 模式总是让人感觉很不优雅。
在更改值后,#default-parameter-values-and-the-arguments-object 下的第一个代码示例不应该在 CodePen 上显示此内容吗?
是的,现在已修复。抱歉,我认为这是一个复制粘贴错误,就像另一个指出的错误一样。
看起来像是粘贴错了?
getValue(25, 30);
// “25.00”
// “28.25” — 应该是 30.00
是的,它已修复。谢谢!
您跳过了使用对象解构的默认命名参数。
或者可能是一个更实用的例子
不错,谢谢。我还没有深入研究解构(只肤浅地研究过),所以我肯定无法对这个主题发表太多意见。但感谢您将其添加到讨论中。
顺便说一句,信息很好!超级有用。
在默认参数出现之前,我真的很喜欢
因为它稍微优雅一些,而且
!=
检查 undefined 和 null(尽管您自己决定是否只关心 undefined),并且它没有||
带来的假值问题。片段中写为注释的一些预期输出是错误的,您可能需要再次检查。
谢谢。只是演示中的注释,现在都正确了。大部分只是复制粘贴错误,是实时演示本身中更改代码的残留物。