在 ES6 中使用默认参数

Avatar of Louis Lazaris
Louis Lazaris

DigitalOcean 为您的旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

我最近开始更多地研究 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() 函数只有一个必填参数:nameyearcolor 参数是可选的,因此如果在调用 getInfo() 时没有提供它们作为参数,它们将被分配默认值

getInfo('Chevy', 1957, 'Green');
getInfo('Benz', 1965); // default for color is "Blue"
getInfo('Honda'); // defaults are 2018 and "Blue"

在 CodePen 上试试

如果没有这种检查和保障措施,任何未初始化的参数将默认为 undefined 值,这通常不希望出现。

您也可以使用真值/假值模式来检查没有值的参数

function getInfo (name, year, color) {
  year = year || 2018;
  color = color || 'Blue';
  // remainder of the function...
}

但这在某些情况下可能会导致问题。在上面的示例中,如果您为年份传入 0 的值,则默认的 2018 将覆盖它,因为 0 被评估为假值。在此特定示例中,您不太可能对此感到担忧,但有许多情况是您的应用程序可能希望将 0 的值作为有效数字而不是假值来接受。

在 CodePen 上试试

当然,即使使用 typeof 模式,您可能也必须进行进一步的检查才能获得真正万无一失的解决方案。例如,您可能希望将一个可选的 回调函数 作为参数。在这种情况下,仅针对 undefined 进行检查是不够的。您还必须检查传入的值是否是一个有效的函数。

所以,这只是 ES6 之前我们如何处理默认参数的总结。让我们看看一个更好的方法。

ES6 中的默认参数

如果您的应用程序出于遗留原因或由于浏览器支持而需要使用 ES6 之前的功能,那么您可能必须执行类似于我上面描述的操作。但 ES6 使此操作变得更加容易。以下是 ES6 及更高版本中定义默认参数值的方法

function getInfo (name, year = 2018, color = 'blue') {
  // function body here...
}

在 CodePen 上试试

就这么简单。

如果将 yearcolor 值传递到函数调用中,则作为参数传入的值将取代在函数定义中定义的参数。这与 ES5 模式完全相同,但没有那么多额外的代码。更容易维护,更容易阅读。

此功能可用于函数头部中的任何参数,因此您可以为第一个参数设置一个默认值,以及两个其他没有默认值的预期值

function getInfo (name = 'Pat', year, color) {
  // function body here...
}

处理省略的值

请注意,在像上面这样的情况下,如果您想省略可选的 name 参数(因此使用默认值)同时包含 yearcolor,则必须将 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');

在 CodePen 上试试

在这种情况下,我将第二个参数传递为 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');

在 CodePen 上试试

请注意,在上面的示例中,如果我更改函数参数的值,这些更改会反映在 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');

在 CodePen 上试试

如演示所示,在严格模式下,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');

在 CodePen 上试试

此示例中需要注意几件事。

首先,包含默认参数不会更改 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"

在 CodePen 上试试

上面代码中需要注意几件事。首先,我允许在函数调用中不包含第二个参数时,通过 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"

在 CodePen 上试试

在上面的示例中,我在 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

在 CodePen 上试试

同样,正如您所预期的那样,您不能从函数参数访问函数体内部定义的变量。

结论

这篇文章涵盖了在 ES6 及更高版本中使用函数默认参数的几乎所有内容。这个功能本身在最简单的形式下非常容易使用,但正如我在这篇文章中讨论的,有很多细节值得理解。

如果您想了解更多关于此主题的信息,以下是一些资料来源。