掌握 JavaScript 函数参数

函数是一段内聚的代码,耦合在一起执行特定的任务。函数使用其参数访问外部世界。

要编写简洁高效的 JavaScript 代码,您必须掌握函数参数。

在这篇文章中,我将以有趣的示例说明 JavaScript 必须有效使用函数参数的所有功能。

函数参数

JavaScript 函数可以有任意数量的参数。让我们分别声明参数个数为 0,1 和 2 个的函数:

// 0 个参数
function zero() {
  return 0;
}

// 1 个参数
function identity(param) {
  return param;
}

// 2 个参数
function sum(param1, param2) {
  return param1 + param2;
}

zero(); // => 0
identity(1); // => 1
sum(1, 2); // => 3

上面的 3 个函数执行了与函数声明的参数个数相同的参数。但是你可以使用少于参数个数的参数执行函数。每一种情况下 JavaScript 都没有生成任何错误。

然而,没有传入参数的参数考虑使用 undefined 值初始化。

举例来说,我们使用一个参数执行函数 sum()(拥有两个参数):

function sum(param1, param2) {
  console.log(param1); // 1
  console.log(param2); // undefined
  return param1 + param2;
}

sum(1); // => NaN

该函数仅仅使用一个参数执行:sum(1)。所以参数 param1 有值 1,第二个参数 param2 使用 undefined 初始化。

param1 + param2 等价于 1 + undefined,所以结果为 NaN

如果必要的话,可以总是验证如果参数是 undefined 时提供默认值。我们使 param2 的默认值为 0

function sum(param1, param2) {
  if (param2 === undefined) {
    param2 = 0;
  }
  return param1 + param2;
}

sum(1); // => 1

这里有一个更好的方法设置默认值。在下一节我们看它是如何工作。

默认参数

ES2015 默认参数功能允许使用默认值初始化参数。这是比上面例子更好且更简洁的方式。

我们使用 ES2015 默认参数设置 param2 默认为 0

function sum(param1, param2 = 0) {
  console.log(param2); // => 0
  return param1 + param2;
}

sum(1); // => 1
sum(1, undefined); // => 1

在函数签名中,现在是 param2 = 0,所以当它没有设置任何值时 param2 默认为 0

现在,如果该函数仅仅使用一个参数执行:sum(1),第二个参数将使用 0 初始化。

注意:如果你设置 undefined 为第二个参数 sum(1, undefined) 参数 param2 同样使用 0 初始化。

参数解构

我特别喜欢的是 JavaScript 函数参数的解构能力。可以行内解构对象或数组参数。

该功能在提取参数对象的一部分时很有用:

function greet({ name }) {
  return `Hello, ${name}!`;
}

const person = { name: 'John Smith' };
greet(person); // => 'Hello, John Smith!'

{ name } 是一个从对象解构出的参数。可以很容易组合使用默认参数和解构:

function greetWithDefault({ name = 'Unknown' } = {}) {
  return `Hello, ${name}!`;
}

greetWithDefault(); // => 'Hello, Unknown!'

{ name = 'Unknown' } = {} 默认为一个空对象。

可以使用组合不同类型的解构的能力。举例来说,我们使用对象和数组解构在相同的参数:

function greeFirstPerson([{ name }]) {
  return `Hello, ${name}!`;
}

const persons = [{ name: 'John Smith' }, { name: 'Jane Doe' }];
greeFirstPerson(persons); // => 'Hello, John Smith!'

[{ name }] 参数解构更加复杂。它首先提取数组的第一项,然后读取该项的 name 属性。

参数对象

JavaScript 函数另一个好功能是使用可变数量参数执行同一函数。

如果你这样做,可以使用特别对象 argumentsarguments 包含所有参数在一个类数组对象中。

举例来说,我们对函数参数求和:

function sumArgs() {
  console.log(arguments);
  // -> { 0: 5, 1: 6, length: 2 }
  let sum = 0;
  for (let i = 0; i < arguments.length; i++) {
    sum += arguments[i];
  }
  return sum;
}

sumArgs(5, 6); // => 11

arguments 包含函数执行时的参数。

arguments 是一个类数组对象(array-like object),所以它不能使用所有的数组方法。

另外一个不同点是每一个函数作用域声明它自己的 arguments 对象。所以可能需要额外的变量来访问外部函数作用域内的 arguments

function outerFunction() {
  const outerArguments = arguments;
  return function innerFunction() {
    // outFunction arguments
    outerArguments[0];
  };
}

箭头函数

有一个特别情况:arguments 不存在于箭头函数内。

const sumArgs = () => {
  console.log(arguments);
  return 0;
};

// throws: "Uncaught ReferenceError: arguments is not defined"
sumArgs();

arguments 没有声明在箭头函数内。但这不是大问题。我们可以使用更高效的扩展参数来访问箭头函数的所有参数。

扩展参数(Rest parameters)

ES2015 扩展参数让你收集函数的所有参数为数组。让我们使用扩展参数对参数求和:

function sumArgs(...numbers) {
  console.log(numbers); // [5, 6]
  return numbers.reduce((sum, number) => sum + number);
}

sumArgs(5, 6); // => 11

扩展参数 ...numbers 保存参数到数组 [5, 6]。由于 numbers 是数组,可以很容易使用所有数组方法。

虽然 arguments 对象不允许在箭头函数内,但扩展参数没有问题:

const sumArgs = (...numbers) => {
  console.log(numbers); // [5, 6]
  return numbers.reduce((sum, number) => sum + number);
};

sumArgs(5, 6); // => 11

如果不想使用扩展参数收集所有的参数,你可以自由组合使用常规参数和扩展参数。

function multiplyAndSumArgs(multiplier, ...numbers) {
  console.log(multiplier); // 2
  console.log(numbers); // [5, 6]
  const sumArgs = numbers.reduce((sum, number) => sum + number);
  return multiplier * sumArgs;
}

multiplyAndSumArgs(2, 5, 6); // => 22

multiplier 是参数值的第一个常规参数,然后 ...numbers 包含所有剩余参数。

注意:每个函数最多可以有一个扩展参数。扩展参数必须位于函数参数列表的最后。

总结

除了基本用法外,JavaScript 在使用函数参数时还提供了许多有用的功能。

您可以在缺少参数时轻松地将其默认为默认值。

JavaScript 解构的所有功能都可以应用于参数。您甚至可以将解构与默认参数结合使用。

arguments 是一个类数组的特殊对象,其中包含调用该函数所用的所有参数。

作为更好的替代方法 arguments,您可以使用扩展参数功能。它也保存参数列表,但是将它们存储到数组中。

另外,您可以将常规参数与扩展参数一起使用,但是后者必须始终在参数列表中位于最后。