ES6在函数参数上有了一些很方便的改变,其一就是参数的默认值可以在写形参的时候直接写
参数默认值的使用
参数的默认值是在参数被省略或者参数为undefined时使用。
function fn(a=1){
console.log("a: "+a);
}
fn() // "a: 1"
fn(undefined) // "a: 1"
fn(2) // "a: 2"
这两种情况看起来似乎是相同的,但在某些情况还是有些区别的,比如传入undefined时arguments的length会增加,而省略不会。
function fn(a=1,b=2){
console.log(arguments.length);
}
fn() // 0
fn(undefined) // 1
fn(undefined,undefined) // 2
参数默认值带来的便利
在ES5中,我们如果要实现参数的默认值,一般是会在函数的第一行用到"||"来给传入的参数赋默认值
function fn(a){
a=a||'world';
console.log('hello '+a);
}
fn();//hello world
fn('friend');//hello friend
当我们省略参数时,a就会自动赋值为'world',当传入参数时,a就会等于输入的参数。但是这个方法有一些缺陷,在传入可以隐式转为假值的值时就会调用默认值,如下面代码
function add(a,b){
a=a||2;
b=b||3;
console.log(a+b);
}
add(); // 5
add(0); // 5
add(0,0); // 5
可以看到,虽然我们传入了0,但是因为这里将0隐式转为false,根据||的用法,如果||判断中第一个值为false,那么就会将||后的值作为这个运算的最后结果,所i有a||2,也就是0||2,最后返回的是2,所以这里即便传入了0,最后a还是等于2。b也同理。
除了使用"||"外,也可以使用arguments来获取参数,从而达到同样的效果。
function fn() {
var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'world';
console.log('hello ' + a);
}
这里通过使用三目运算符判断是否有参数,若有的话将a赋值为传入的参数,没有的话则赋值为'world'。(这部分代码是我用下面要写的ES6在babel下转换生成的)
在ES6中,我们可以很方便地实现函数参数的默认值,形式几乎就是在参数位置直接赋值,很容易记住
function fn(a='world'){
console.log('hello '+a);
}
fn();//hello world
fn('friend');//hello friend
像这样直接在参数位置赋给a默认值,十分方便。可能一个参数看起来和写一句var a=a||'world'差别不大,但是如果参数多的话,ES6提供的便利性就很明显了,而且这样也避免了一些使用||的坑。
参数默认值表达式
参数默认值不仅可以是一些简单值,也可以是一些合法的表达式,甚至是函数调用。
function fn(a=x+1,b=num()){
console.log(a+b);
}
function num(){
return 10;
}
var x=5;
fn(); // 16
var x=6;
fn(); // 17
上面对的代码使用的两个默认值,一个是相加的表达式,一个是函数调用。而从上面两个fn()打印出的结果不同可以看出,作为默认参数的表达式是惰性求值的,它们只在需要的时候运行。
还有一点要注意的是,参数默认值是在其自己的作用域中,即上面的代码中,a=x+1,首先是在a所在的作用域中寻找x,在当前作用域中找不到x后在外部作用域寻找x,找到后才赋值给这里的a,可以使用下面的代码更明显地看出参数默认值所在的作用域。
var z=2;
function fn(z=z){
console.log(z);
}
fn(); // Uncaught ReferenceError: z is not defined at fn
这里在调用方法fn时,因为没有传入参数,所以要使用参数默认值,而首先在当前作用域中寻找z,而当前作用域中z是个未被初始化的参数变量,所以将z作为参数默认值会报错。
参数默认值带来的坑
ES6的默认参数虽然方便,但也有一些坑:
1.使用默认参数后不能再在函数里面使用let和const声明该变量,但是var可以
function fn(a=2){
let a=1;
}
fn()//Uncaught SyntaxError: Identifier 'a' has already been declared
fn(1)//Uncaught SyntaxError: Identifier 'a' has already been declared
这个可以在阮一峰的ES6入门中得到解释,“参数变量是默认声明的”,所以let和const是不能再次使用的,这里的报错也可以看出'a'已经被声明了。
2.使用参数默认值时,函数不能有同名参数
function fn(a,a,b){
console.log(a+''+b);
}
fn(1,2,3,4);//34
function fn(a,a=2,b){
console.log(a+''+b);
}//Uncaught SyntaxError: Duplicate parameter name not allowed in this context
function fn(a,a,b=2){
console.log(a+''+b);
}//Uncaught SyntaxError: Duplicate parameter name not allowed in this context
这里可以看到在没给参数赋默认值之前,传入相同的参数名是不会报错的,也可以传入参数执行结果,但是如果给参数赋默认值的话,不管赋默认值的是重复的参数还是没重复的参数,都会报错。
3.函数的length值会失真
指定了函数的参数默认值后,函数的length值会返回没有指定默认值的参数个数
(function fn(a,b,c){}).length//3
(function fn(a,b,c=1){}).length//2
(function fn(a,b=1,c=1){}).length//1
(function fn(a=1,b,c){}).length//0 a后面的b,c不带默认值
就上面代码来看,第一行的代码的函数参数中没有默认参数,所以length值为3,第二,三行的代码中的length值等于参数个数减去带默认值的参数的个数。而第四行我们可以看到,只有一个带默认值的参数,但是得到的length值却为0,是因为带默认参数的值后面还有不带默认值的参数,所以就会使length的值为0。
说完这部分坑,说说ES6另一个便利的地方,rest参数
在ES6之前,我们如果不确定会传入多少个参数,可以使用arguments来获取多传入的参数
function fn(a){
if(arguments.length>1)
for(let i=0;i<arguments.length;i++)
console.log(arguments[i]);
else
console.log(a);
}
fn(1);//1
fn(1,2,3,4);//1 2 3 4
而在ES6中,我们可以这样写
function fn(a, ...rest) {
console.log(a);
for (let i = 0; i < rest.length; i++)
console.log(rest[i]);
}
fn(1);//1
fn(1,2,3,4);//1 2 3 4
这样得到的rest为一个数组对象。同样的,在使用rest参数时,也有要注意的地方,rest参数必须放在参数最后面的地方,否则会报错
function fn(a, ...rest,b) {
console.log(a);
for (let i = 0; i < rest.length; i++)
console.log(rest[i]);
}//Uncaught SyntaxError: Rest parameter must be last formal parameter
参考文档:阮一峰的《ECMAScript6入门》
Kyle Simpson的《你不知道的JavaScript 下卷》
ES6学习笔记目录(持续更新中)