我们知道,参数分为形参(parameter)和实参(argument),形参是指函数定义时的参数,实参是指真正传入函数的参数。下面,我们将从多个角度来分析,ES6中,参数的一些行为。
参数的默认值####
函数中参数的默认值这一设定尤为重要,不仅可以避免一些不必要的错误,还能简化过程,好处很多。下面我们来看看,在ES5中,参数的默认值是如何设定的:
function foo(num1,num2){
num1 = num1||10;
num2 = num2||5;
console.log(num1,num2);
}
foo(1,2) //1,2
foo(1) //1,5
foo(undefined,2) //10,2
通常是用||
运算符来做默认值处理,判断传入的参数的真假,如果为真,用传入的参数,为假值,用默认的参数。当然,这种方式有一定的漏洞的,比如当我们传入的参数是0
的时候。
foo(0,2) //10,2
就有问题了。其实得到这个结果也是意料之中的,0
的隐式转换为布尔值的结果就是false
,那么应该如何改进呢?在ES5中,人们是这么处理的:
function foo(num1,num2){
num1 = num1 !== undefined?num1:10;
num2 = num2 !== undefined?num2:5;
console.log(num1,num2);
}
foo(1,2) //1,2
foo(1) //1,5
foo(undefined,2) //10,2
foo(0,2) //0,2
确实,这样写看起来就万无一失了,但是显得太臃肿了不是吗,在ES6中,我们可以直接使用如下方式设置默认值:
function foo(num1=10,num2=5){
console.log(num1,num2);
}
foo(1,2) //1,2
foo(1) //1,5
foo(undefined,2) //10,2
foo(0,2) //0,2
下面,让我们来思考一个问题,默认值到底是如何影响参数的呢?传入的参数我们知道,可以通过arguments
对象获得,那么,如果传入的参数在函数体内被改变,那么对应的arguments
也会改变吗?请看下面的例子:
function foo(num1,num2){
console.log(num1 === arguments[0]);
console.log(num2 === arguments[1]);
num1 = 'a';
num2 = 'b';
console.log(num1 === arguments[0]);
console.log(num2 === arguments[1]);
}
foo(1,2); //true
//true
//true
//true
由此可以看出,在参数被修改的时候,arguments
也会跟着修改,并且保持与修改的参数一致。这是在非严格模式下,那么在严格模式下呢?请看如下例子:
'use strict'
function foo(num1,num2){
console.log(num1 === arguments[0]);
console.log(num2 === arguments[1]);
num1 = 'a';
num2 = 'b';
console.log(num1 === arguments[0]);
console.log(num2 === arguments[1]);
}
foo(1,2); //true
//true
//false
//false
正如你所看到的,在严格模式下,arguments
并没有修改。那么,在ES6中的默认值呢?看如下例子:
function foo(num1,num2=2){
console.log(arguments.length);
console.log(num1 === arguments[0]);
console.log(num2 === arguments[1]);
num1 = 'a';
num2 = 'b';
console.log(num1 === arguments[0]);
console.log(num2 === arguments[1]);
}
foo(1); //1
//true
//false
//false
//false
在严格模式下:
'use strict'
function foo(num1,num2=2){
console.log(arguments.length);
console.log(num1 === arguments[0]);
console.log(num2 === arguments[1]);
num1 = 'a';
num2 = 'b';
console.log(num1 === arguments[0]);
console.log(num2 === arguments[1]);
}
foo(1); //1
//true
//false
//false
//false
通过arguments.length
,传入的参数个数是1
,我们可以明白,其实arguments
真正收集的不是默认值,而是输入的参数,并且只要加入了默认值,无论是在严格模式,还是非严格模式,参数arguments
都不会改变,只会保留初始值,也就是函数调用的时候传入的最初的值。
不仅如此
ES6默认值的用法不仅如此,它不仅可以传入原始值,还可以是函数调用,甚至是参数本身,具体例子如下:
function add(num1,num2){
return num1+num2
}
function foo(num1=10,num2=add(num1,5)){
console.log(num1,num2);
}
foo(1,2) //1,2
foo(1) //1,6
foo(undefined,1) //10,1
foo() //10,15
当然,如果调用的默认值是不存在的,那么会报错。比如:
function add1(num1,num2){
return num1+num2
}
function foo(num1=10,num2=add(num1,5)){ //报错,ReferenceError,add不存在
console.log(num1,num2);
}
当然,还有一种不太明显的,就是第一个参数把第二个参数当默认值:
function foo(num1=num2,num2=10){
console.log(num1,num2);
}
foo(1,2) //1,2
foo(undefined,2) //报错,ReferenceError,num2不存在
因为暂时性死区,在使用num2
的时候,并没有申明,其实上述的代码可以大概理解为在函数内部是这样的:
function foo(num1,num2){
let num1;
num1 = arguments[0] !== undefined?arguments[0]:num2;
let num2;
num2 = arguments[1] !== undefined?arguments[1]:10;
console.log(num1,num2);
}
由此,可以看出,使用num2
的时候并没有申明,所以会报错。
还有一点值得注意,函数参数的有自己单独的作用域和自己的暂时性死区(TDZ),和函数本身的作用域是分开的。也就是说,参数的默认值不能使用函数内部申明的变量。
参数的默认值就告一段落了,下面说一个ES6引入的新的东西,作用和arguments
类似,但是又不完全相同
剩余参数(Rest Parameters)####
剩余参数
的申明是使用...
3个点加在参数名前。我们知道,在JavaScript中,参数的个数,传入的多少并不重要,需求2个参数,但是传入了3个参数,那么最后一个参数就会被舍去;需求2个参数,但是传入了1个参数,第二个参数就是undefined
,这个本身不会报错。那么剩余参数
的作用就是类似于arguments
,把剩下的参数以数组的形式收集起来,具体操作如下:
function foo(num1,...num2){
console.log(num2);
}
foo(0,1,2,3,4,5); //[1,2,3,4,5]
正如你说看到的,num1
是0
,剩下的参数1,2,3,4,5
全部被num2
以数组的形式收集了起来。
剩余参数
的概念并不复杂,但是还是需要注意2点:
-
剩余参数
后面不能再跟参数,否则会报错:
function foo(num1,...num2,num3){ //报错,SyntaxError,后面不能再跟参数
console.log(num2);
}
也就是说,剩余参数
只能是函数的最后一个参数。
-
剩余参数
不能运用在对象的setter
方法中。例如:
let a = {
set name(...value) { //报错,SyntaxError
// do something
}
}
至于理由,很简单,因为setter
方法本身只能接受单个数,而剩余参数
是一个数组,不符合要求,当然会报错。
最后再讲一点,如果一个函数的参数只有剩余参数
,那么此时,剩余参数
和arguments
表达的差不多是一个意思,只不过剩余参数
是一个更纯粹的数组。arguments
还背负着自己的历史使命,比如callee
,之类的。