Node.js 12实战
上QQ阅读APP看书,第一时间看更新

3.3.1 参数的默认值

在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法,例如:

function log(x, y) {
 y = y || 'World';
  console.log(x, y);
}
log('Hello')           // Hello World
log('Hello', 'China')  // Hello China
log('Hello', '')       // Hello World

上面的代码检查函数log的参数y有没有赋值,如果没有,就指定默认值为World。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,该赋值就不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。

为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。

if (typeof y === 'undefined') {
  y = 'World';
}

ES6允许为函数的参数设置默认值,即直接写在参数定义的后面,例如:

function log(x, y = 'World') {
  console.log(x, y);
}
log('Hello')                 // Hello World
log('Hello', 'China')        // Hello China
log('Hello', '')             // Hello

可以看到,ES6的写法比ES5简洁许多,而且非常自然,例如:

function Point(x = 0, y = 0) {
  this.x = x;
  this.y = y;
}
var p = new Point();
p // { x: 0, y: 0 }

除了简洁,ES6的写法还有两个好处:首先,阅读代码的人可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来代码的优化,即使未来的版本在对外接口中彻底拿掉这个参数,也不会导致以前的代码无法运行。

参数变量是默认声明的,所以不能用let或const再次声明。

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

上面的代码中,参数变量x是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。

使用函数默认参数时,不允许有同名参数,否则会报错:

// 不报错
function fn(name,name){
 console.log(name);
}
// 报错
//SyntaxError: Duplicate parameter name not allowed in this context
function fn(name,name,age=17){
 console.log(name+","+age);
}

只有在未传递参数或者参数为undefined时,才会使用默认参数,null值被认为是有效的值传递:

function fn(name,age=17){
    console.log(name+","+age);
}
fn("Amy",null); // Amy,null

参数默认值可以与解构赋值的默认值结合起来使用,例如:

function foo({x, y = 5}) {
  console.log(x, y);
}
foo({}) // undefined, 5
foo({x: 1}) // 1, 5
foo({x: 1, y: 2}) // 1, 2
foo() // TypeError: Cannot read property 'x' of undefined

上面的代码使用了对象的解构赋值默认值,而没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值而生成。如果函数foo调用时参数不是对象,变量x和y就不会生成,从而报错。如果参数对象没有y属性,y的默认值5才会生效。

再来看看下面两种写法有什么差别?

// 写法一
function m1({x = 0, y = 0} = {}) {
  return [x, y];
}
// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。在使用中的差异:

// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]
// x和y都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]
// x有值、y无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x和y都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]

通常情况下,定义了默认值的参数应该是函数的尾参数,因为这样比较容易看出来到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。

// 例一
function f(x = 1, y) {
  return [x, y];
}
f()       // [1, undefined]
f(2)      // [2, undefined])
f(, 1)  // 报错
f(undefined, 1) // [1, 1]
// 例二
function f(x, y = 5, z) {
  return [x, y, z];
}
f()       // [undefined, 5, undefined]
f(1)      // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]

上面的代码中,有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式地输入undefined。

函数参数默认值存在暂时性死区,在函数参数默认值表达式中,还未初始化赋值的参数值无法作为其他参数的默认值。

function f(x,y=x){
    console.log(x,y);
}
f(1);  // 1 1
function f(x=y){
    console.log(x);
}
f();  // ReferenceError: y is not defined

不定参数用来表示不确定参数个数,形如...变量名,由...加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。

function f(...values){
    console.log(values.length);
}
f(1,2);      //2
f(1,2,3,4);  //4