默认参数
在ES5的版本中,我们习惯于通过下面的模式创建函数并为参数赋予默认值:
function makeRequest(url,timeout,callback){
timeout=timeout || 200;
callback=callback || function(){};
//其余代码
}
我们认为,在含有逻辑操作符的表达式中,前一个操作数的值为假时,总会返回后一个值。对于函数的命名参数,如果不显式传值,则其默认值为undefined。因此我们经常使用逻辑或操作符来为缺失的参数提供默认值。
然而这个方法有一个致命的缺陷:当给timeout传入值0,即使这个值是合法的,也会被视为一个假值。(这很好理解)
这种情况下,更安全的选择貌似就是通过typeof检查参数类型:
function makeRequest(url,timeout,callback){
timeout=(typeof timeout !== "undefined") ? timeout : 200;
callback=(typeof callback !== "undefined") ? callback : function(){};
//其余代码
}
尽管这种方法看上去更安全了,而且现在流行的JavaScript库中几乎都使用类似的模式进行默认补全,但是我们仍感觉这很“繁琐”——它仍需额外的代码来执行这种非常基础的操作。这让我们很不爽。
function makeRequest(url,timeout=200,callback=function(){}){
//其余代码
}
ES6站了出来,简化了为形参提供默认值的过程:
如上,只有第一个参数被认为 总是要 为其传入值的,其他两个参数可以有默认值,而且不需要添加任何校验值是否缺失的代码。
当然后两个参数也可以有传入值,这时,传入值会覆盖默认值。
makeRequest("/foo");
makeRequest("/foo",500);
makeRequest("/foo",500,function(body){
doSomething(body);
});
默认参数值对arguments对象的影响
arguments对象——传入参数(数组)对象
切记:当使用默认参数时,arguments对象的行为与以往不同。
ES5非严格模式下,函数命名参数的变化会体现在arguments对象中:
function mixArgs(first,second){
console.log(first===arguments[0]);
console.log(second===arguments[1]);
first="c";
second="d";
console.log(first===arguments[0]);
console.log(second===arguments[1]);
}
mixArgs("a","b"); //true true true true
在非严格模式下,命名参数的变化会同步更新到arguments对象中,所以当first和second被赋予新值时,arguments[0]和arguments[1]相应地也就更新了,最终所有===全等比较就为true了。
故而在ES5严格模式下,取消了arguments对象的这个令人困惑的行为:
function mixArgs(first,second){
"use strict";
console.log(first===arguments[0]);
console.log(second===arguments[1]);
first="c";
second="d";
console.log(first===arguments[0]);
console.log(second===arguments[1]);
}
mixArgs("a","b"); // true true false false
在ES6中,如果一个函数使用了默认参数值,则无论是否显式定义了严格模式,arguments对象的行为都将与ES5严格模式下保持一致。
(默认参数值的存在使得arguments对象保持与命名参数分离)
function mixArgs(first,second="b"){
console.log(arguments.length);
console.log(first===arguments[0]);
console.log(second===arguments[1]);
first="c";
second="d";
console.log(first===arguments[0]);
console.log(second===arguments[1]);
}
mixArgs("a"); // 1 true false false false
默认参数表达式
就像这样:
function getValue(){return 5;};
function add(first,second=getValue()){return first+second;};
console.log(add(1,1)); // 2
console.log(add(1)); //6
注意: 当使用函数调用结果作为默认参数值时,如果忘记写小括号,例如:second=getValue,则最终传入的是对函数的引用,而不是函数调用的结果:
function getValue(){return 5;};
function add(first,second=getValue){return first+second;};
console.log(add(1,1)); // 2
console.log(add(1)); // 1 function getValue(){return 5;}
默认参数的临时死区
前文中,我提到了“let和const变量的临时死区TDZ”,其实默认参数也有临时死区:在这里的参数不可访问。
与let相似,定义参数时会为每个参数创建一个新的 标识符绑定,该绑定在初始化之前不可被引用。
function getValue(value){
return value+5;
}
function add(first,second=getValue(first)){
return first+second;
}
console.log(add(1,1)); // 2
console.log(add(1)); // 7
当除此执行函数add()时,绑定first与second被添加到一个专属于函数参数的临时死区(与let行为类似)
由于初始化second时first已经被初始化,所以他可以访问first的值,但是反过来就错了:
function add(first=second,second){
return first+second;
}
console.log(add(1,1)); // 2
console.log(add(1)); //抛出错误!
处理不定参数
在函数的命名参数前添加三个点(…)就表明这是一个不定参数,该参数为一个数组,包含着自他之后传入的所有参数,通过这个数组名即可注意访问里面的参数。
function pick(object,...keys){
let result=Object.create(null);
for(let i=0,len=keys.length;i<len;i++){
result[keys[i]]=object[keys[i]];
}
return result;
}
不定参数的使用限制
- 每个函数最多只能声明一个不定参数,而且一定放在所有参数的末尾
- 不定参数不能用于对象字面量setter中(对象字面量setter的参数有且只能有一个)
增强的Function构造函数
Function构造函数通常被我们用来动态创建新的函数。这种构造函数接收字符串形式的参数,分别为函数的参数及函数体:
var add=new Function("first","second","return first+second");
console.log(add(1,1)); // 2
ES6为其增加了默认参数和不定参数的功能。
var pickFirst=new Function("...args","return args[0]");
console.log(pickFirst(1,2)); // 1