
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