搞懂 JavaScript 默认参数,看这一篇就够了

JavaScript语言自ECMAScript 2015引入了函数的默认参数。如果参数未提供给函数调用,则允许开发人员使用默认值初始化函数。以这种方式初始化函数参数可以使得函数更易于阅读且不易出错。为函数提供默认行为,可以帮助避免因传入未定义的参数和解构不存在的对象而导致的错误。

本文将回顾形参(Arguments)和实参(Parameters)之间的区别,介绍如何在函数中使用默认参数,以及查看支持默认参数的可选方法,并说明哪些类型的值和表达式可以用作默认参数。

形参和实参

在解释函数的默认参数之前,首先要知道什么样的参数可以作为默认参数。因此,我们先来回顾函数中形参和实参之间的区别。

以下代码块将创建一个函数,并返回给定数字的三次方,函数的参数用x表示:

function cube(x) {
  return x * x * x
}

例子中的变量x是实参:一个传递给函数的命名变量。实参必须始终包含在变量中,并且绝不能有直接值。

再看下一个代码块,将调用刚刚创建的cube函数:

cube(10)

得到以下输出:

1000

这里的10是形参:一个在调用函数时传递给函数的值。通常这个值也包含在变量中,例如:

// 定义一个number变量
const number = 10

// 调用cube函数
cube(number)

这将和之前的产生相同的结果:

1000

如果你不将参数传递给需要参数的函数,则函数将隐式使用undefined作为值:

// 调用cube函数,而且不传任何参数
cube()

这时候的返回结果如下:

NaN

在这种情况下,cube()试图计算undefined * undefined * undefined的值,从而导致出现结果NaN

这种自动行为有时会出现问题。在某些情况下,即使没有参数传递给函数,你可能也希望参数具有值。这时候就该默认参数上场了。

默认参数语法

随着ES2015中添加了默认参数,现在你可以为任何参数分配默认值——在没有参数的情况下调用该函数将使用该默认值而不是undefined。首先展示如何手动实现默认参数的行为,然后再引导你如何使用默认参数。

如果没有默认参数,你必须显式检查undefined值才能设置默认值,如下例所示:

// 手动检查参数x是否为undefined
function cube(x) {
  if (typeof x === 'undefined') {
    x = 5
  }

  return x * x * x
}

cube()

使用条件语句来检查值是否自动提供为undefined,然后将x的值设置为5。得到以下输出:

125

使用默认参数可以通过更少的代码实现相同的目标。通过赋值运算符(=)为cube中的参数设置默认值,如下所示:

// 定义cube函数时使用默认参数
function cube(x = 5) {
  return x * x * x
}

现在,当在没有参数的情况下调用cube函数时,会分配5给变量x,并返回计算值而不是返回NaN

cube();
//Output
125

传递参数时,仍将按预期运行,忽略默认值:

cube(2);
//Output
8

但是,需要注意的是,如果传递给函数的是显式的undefined,那么默认参数值仍旧会将其覆盖,也就是说,函数实际上仍然会采用默认的参数值,如下所示:

cube(undefined)
//Output
125

默认参数数据类型

任何原始值或对象都可以用作默认参数值。默认参数有很多灵活的使用方式。

可以使用数字、字符串、布尔值、对象、数组和空值作为默认值设置参数。此示例使用箭头函数语法:

// 使用不同的数据类型作为函数的默认参数值
const defaultNumber = (number = 42) => console.log(number)
const defaultString = (string = 'Shark') => console.log(string)
const defaultBoolean = (boolean = true) => console.log(boolean)
const defaultObject = (object = { id: 7 }) => console.log(object)
const defaultArray = (array = [123]) => console.log(array)
const defaultNull = (nullValue = null) => console.log(nullValue)

当这些函数在没有参数的情况下被调用时,都将使用默认值:

defaultNumber()
defaultString()
defaultBoolean()
defaultObject()
defaultArray()
defaultNull()
//Output
42
"Shark"
true
{id: 7}
(3) [123]
null

注意,默参数中创建的任何对象在每次调用函数时都会创建。默认参数的常见用例之一是使用此行为从对象中获取值。如果试图从不存在的对象中解构或访问值,则会抛出错误。但是,如果默认参数是空对象,那就只会给出undefined值而不是抛出一个错误:

// 使用对象作为默认参数
function settings(options = {}) {
  const { theme, debug } = options

  // Do something with settings
}

可以避免因解构不存在的对象而导致的错误。

使用多个默认参数

可以在函数中使用任意数量的默认参数。

首先,声明一个具有多个默认参数的sum()函数:

// 定义一个有2个默认参数的函数
function sum(a = 1, b = 2) {
  return a + b
}

sum()

得到以下默认计算:

//Output
3

此外,参数中使用的值可用于所有后续默认参数。例如,createUser函数创建了一个用户对象userObj作为第三个参数,函数本身所做的就是返回带有前两个参数的userObj

// 第三个默认对象默认参数的属性值可以是其他默认参数
function createUser(name, rank, userObj = { name, rank }) {
  return userObj
}

// 创建用户
const user = createUser('Jean-Luc Picard''Captain')

调用user,得到以下信息:

//Output
{name: "Jean-Luc Picard", rank: "Captain"}

通常建议将所有默认参数放在参数列表的末尾,以便省略可选值。如果先使用默认参数,则必须显式传递undefined才能使用默认值。

下面是一个默认参数在参数列表开头的示例:

// 默认参数在参数列表的开头
function defaultFirst(a = 1b) {
  return a + b
}

调用此函数时,必须用两个参数调用defaultFirst()

defaultFirst(undefined, 2)

结果如下:

//Output
3

下面是默认参数放在参数列表末尾的示例:

// 默认参数在参数列表的末尾
function defaultLast(a, b = 1) {
  return a + b
}

defaultLast(2)

产生相同的值:

//Output
3

虽然两个函数结果相同,但默认值放在最后使得函数调用更清晰。

一个真实的例子,有一个函数,将创建一个DOM元素,并添加文本标签和类(如果存在的话)。

// 定义一个创建元素的函数
function createNewElement(tag, text, classNames = []) {
  const el = document.createElement(tag)
  el.textContent = text

  classNames.forEach(className => {
    el.classList.add(className)
  })

  return el
}

可以通过数组中的类调用函数:

const greeting = createNewElement('p''Hello!', ['greeting''active'])

调用greeting将给出以下值:

<p class="greeting active">Hello!</p>

但是,如果将classNames数组排除在函数调用之外,仍然可以工作。

const greeting2 = createNewElement('p''Hello!')

greeting2现在具有以下值:

<p>Hello!</p>

在此示例中,forEach()可用于空数组而不会出现问题。如果未在默认参数中设置该空数组,将收到以下错误:

//Output
VM2673:5 Uncaught TypeErrorCannot read property 'forEach' of undefined
    at createNewElement (<anonymous>:5:14)
    at <anonymous>:12:18

函数调用作为默认参数

除了原始值和对象外,调用函数的结果也可以用作默认参数。

下面的代码块将创建一个返回随机数的函数,然后将结果用作cube函数中的默认参数值:

// 定义一个函数,返回1-10的随机数
function getRandomNumber() {
  return Math.floor(Math.random() * 10)
}

// 使用随机数函数作为默认参数
function cube(x = getRandomNumber()) {
  return x * x * x
}

现在调用不带参数的cube函数,每次调用都可能会产生不同的结果:

cube();
cube();

函数调用的输出如下:

//Output
512
64

你甚至可以使用内置方法,例如Math对象上的方法,将函数调用中返回的值用作另一个函数的参数。

以下示例为x分配了一个随机数,并将x用作cube函数的参数。然后y参数计算数字的立方根并检查xy是否相等:

// 参数x的默认值为随机数函数
// 参数y的默认值为参数x的立方根
function doesXEqualY(x = getRandomNumber(), y = Math.cbrt(cube(x))) {
  return x === y
}

doesXEqualY()

结果如下:

//Output
true

默认参数甚至可以是函数定义,如下例所示,将参数定义为inner函数并返回parameter的函数调用:

// 使用匿名函数作为默认参数
function outer(
  parameter = function inner() {
    return 100
  }
) {
  return parameter()
}

// 调用other函数
outer()
//Output
100

每次调用outer函数,都会从零开始创建inner函数。

总结

通过这篇文章,你将了解函数的默认参数是什么以及如何使用它们。默认参数可以用来帮助我们保持函数的简洁和易读。你还可以在前面将空对象和数组分配给参数,从而在处理从对象中检索值或循环遍历数组等情况时降低复杂性和代码行数。