Menu

JavaScript 默认参数:可选参数与后备值

JavaScript 中的参数是怎么工作的:默认值、只由 undefined 触发的规则、求值顺序,以及形参和实参的区别。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

形参与实参

这两个词经常被混用。形参是函数定义中的名字。实参是调用时传入的值。这和 Python 以及大多数其他语言中的概念相同:

a 和 b 是形参,2 和 3 是实参。JavaScript 按位置绑定它们:第一个实参对应第一个形参,依此类推。区别虽小,但错误信息和 MDN 用的就是这套术语。

JavaScript 对参数个数很宽松

和很多语言不同,JavaScript 不在乎你传入的实参是太少还是太多。缺少的参数会变成 undefined,多出来的则被悄悄忽略:

第一次调用打印 Hello, Ada undefined:last 没有得到值,所以是 undefined,而模板字符串会毫不在意地把它插进去。没有错误,也没有警告。这种宽松有时很有用,有时却是 bug 的源头,而这正是默认参数存在的原因。

设置默认值

在参数名后面加上 = 和一个值。如果调用者没有传入这个实参(或传入 undefined),默认值就会生效:

三次调用都能运行。第一次和第三次使用了默认值,第二次使用了传入的值。这是 ES6 语法。在 2015 年之前,你必须在函数体内写 name = name || "friend",而它在处理 0 和 "" 这类假值时有自己的 bug。

默认值可以是任意表达式,不只是字面量:

这个表达式会在每次调用时求值,而不是在函数定义时只求值一次。所以不存在 Python 中那种可变默认值的陷阱:每次调用都会得到一个新的 new Date()。

只有 undefined 会触发默认值

这条规则常常让人意外。默认值在实参为 undefined 时生效,而不是在实参为假值时生效,实参为 null 时也不会生效:

只有最后一次调用使用了 "friend"。显式传入 null,意思就是你在说“这个值是 null”,JavaScript 会照你说的做。空字符串和零也一样:它们是值,不是缺失的实参。

如果你希望把 null 和缺失同等对待,就得自己处理:

?? 运算符(空值合并)把 null 和 undefined 都视为“缺失”。后面的章节会详细讲解。

默认值可以引用前面的参数

参数从左到右求值,所以后面的默认值可以使用它之前的任何参数:

只传一个实参,得到的是一个立方体。传两个实参,得到的是一个底面为正方形的棱柱。方向很重要:你不能引用一个还没有声明的参数:

function bad(a = b, b = 1) {
  return a + b;
}

bad();   // ReferenceError: Cannot access 'b' before initialization

规则和用 let 声明的变量一样:先使用后声明会出错。

默认值与解构配合使用

你可以在解构参数的同时给出默认值。对于“选项对象”形式的参数,这是一种常见的写法:

这里有两层默认值在起作用。内层的默认值(role = "member"、active = true)补全缺失的属性。外层的 = {} 处理调用者完全没有传入实参的情况:没有它,createUser() 会尝试解构 undefined 并抛出异常。

这种写法一开始看起来很密集,但在现代 JavaScript 代码库中随处可见。一旦你一眼就能认出 { ... } = {} 表示“带默认值的选项”,读起来就很快了。

跳过中间的参数

JavaScript 没有 Python 那样的关键字参数。要跳过中间的某个参数并使用它的默认值,你需要显式传入 undefined:

给 prefix 传入 undefined 会保留默认值。这种写法很难看。如果你发现自己经常在调用中写 undefined,就说明该换成选项对象了:

现在调用者直接写出想要覆盖的选项名,顺序也无关紧要了。

默认值不计入 length

一个很少有影响、但偶尔会让人困惑的细节。函数的 length 属性报告的是第一个带默认值的参数之前的参数个数:

一些会检查函数的库(比如某些测试工具和依赖注入工具)用 length 来计算“必需”参数的个数。知道有这条规则就好;除非你在编写这类工具,否则不必死记。

下一章:剩余参数与展开

默认值处理的是事先知道参数的情况。但有时你并不知道:你希望函数能接收任意数量的实参,或者把一组实参转交给另一个函数。这就是 ...rest 和展开运算符的用途,下一章就会讲到。

常见问题

JavaScript 中如何设置参数的默认值?

在函数签名中,参数名后面加上 = 和默认值:function greet(name = 'friend') { ... }。如果调用时没有传值(或传入 undefined),就会使用默认值。这是 ES6 语法,所有现代 JavaScript 环境都支持。

形参和实参有什么区别?

形参是函数定义中的名字:function add(a, b) 有两个形参 a 和 b。实参是调用时实际传入的值:add(2, 3) 传入了实参 2 和 3。阅读错误信息和文档时,这个区别很重要。

传入 null 时默认参数会生效吗?

不会。只有当实参是 undefined(或者完全没有传入)时,默认值才会生效。显式传入 null 的意思是“我给了你一个值,这个值是 null”,于是默认值被跳过。在 null 和 undefined 是同一回事的语言中待过的人,常常会在这里犯错。

默认值可以引用另一个参数吗?

可以。参数从左到右求值,所以后面参数的默认值可以使用前面的参数:function box(width, height = width)。你也可以在默认值中调用函数,比如 function log(msg, time = Date.now()),这个表达式在每次调用时都会重新执行。

Coddy 编程语言插图

和 Coddy 一起学编程

开始