在ES6中,可以为函数定义默认参数,而在ES6之前,可能需要在函数体内添加额外的代码来检查参数是否存在,如若不存在则手动赋一个默认值。
- 在ES5中模拟默认参数
function makeRequest1(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function () { };
console.log(timeout);
console.log(callback);
}
在上面的函数中,timeout和callback是可选参数,如果用户没有传值,通过逻辑或操作符来为缺失的参数提供默认值。但是这个方法有缺陷,比如想给函数的第二个参数传递0,即使这个值是合法的,也会被认为是假值,而使用默认值。
let url = "https://www.baidu.com/";
makeRequest1(url); // 2000 ƒ () { }
makeRequest1(url, 0); // 2000 ƒ () { }
为了解决上面的缺陷,更安全的方式时通过typeof检查参数类型。这种方式也是一种常见的模式,在流行的JavaScript库中均使用类似的模式进行补全。
function makeRequest2(url, timeout, callback) {
timeout = (typeof timeout !== "undefined") ? timeout : 2000;
callback = (typeof callback !== "undefined") ? callback : function () { };
console.log(timeout);
console.log(callback);
}
makeRequest2(url); // 2000 ƒ () { }
makeRequest2(url, 0); // 0 ƒ () { }
- ES6中的默认参数值
function makeRequest(url, timeout = 2000, callback = function () { }) {
console.log(timeout);
console.log(callback);
}
makeRequest(url); // 2000 ƒ () { }
makeRequest(url, 0); // 0 ƒ () { }
// 传入undefined也会使用默认值
makeRequest(url, undefined); // 2000 ƒ () { }
这个函数中,只有第一个参数被认为是总要传值的,其他两个参数都有默认值.而且也不需要添加任何校验值是否缺失的代码,函数体会更小 .
如果不想使用默认值,除了传递自己的值之外,传递null也会被认为是有效值,而不使用默认值.
makeRequest(url, null, null); // null null
- 默认参数值对arguments对象的影响
在ES6中,在函数体中修改命名参数不会同步到arguments参数中(与ES5中的严格模式保持一致不进行同步,而ES5的非严格模式会保持同步)。而函数未传入值使用默认值时,默认值也不会同步到arguments参数中。
function mixArgs(first, second = "b") {
console.log(first === arguments[0])
console.log(second === arguments[1])
first = "c";
second = "d";
console.log(first === arguments[0])
console.log(second === arguments[1])
}
即使first second的值在函数体中发生了变化,arguments对象也不会被同步修改,如果是ES5中非严格模式,则全部输出为true
mixArgs("a", "b"); // true true false false
如果第二个参数不传,默认值也不会出现在arguments数组当中。
mixArgs("a"); // true false false false
此时不会因为默认值而导致arguments发生变化,此时arguments的长度为1,arguments[1]为空
- 默认参数表达式
关于默认参数表达式,最有趣的特性可能是非原始值传参了。举个例子,你可以通过函数执行来得到默认参数的值,就像这样:
function getValue(){
return 5;
}
function add(first,second = getValue()){
return first + second;
}
// 2
console.log(add(1,1));
// 6
console.log(add(1));
在这段代码中,如果不传入最后的一个参数,就会调用getValue()函数来得到默认的值。
但是,初次解析函数时并不会执行函数,而是当调用add函数且不传入第二个参数时才会调用。修改上面的代码如下
let value = 5;
function getValue(){
return value ++;
}
function add(first,second = getValue()){
return first + second;
}
// 2
console.log(add(1,1));
// 6
console.log(add(1));
// 7
console.log(add(1));
在此示例中,变量value的值为5,每次调用getValue方法时加1.第一次调用add(1)为6,第二次调用返回7,因为变量value已经增加了1。但解析函数和第一次add(1,1)时value的值并没有发生改变。
这里还有一点需要注意的是,如果getValue后面的括号没有写的话,则默认参数是对函数的引用。
let value = 5;
function getValue(){
return value ++;
}
function add(first,second = getValue){
return first + second;
}
// 1function getValue(){
// return value ++;
// }
console.log(add(1));
此时的输出就变为了1加上函数定义字符串了,而非执行函数时的返回值。
正因为默认参数是在函数调用时求值,所以可以使用先定义的参数作为后定义参数的默认值,就像这样:
function add(first,second = first){
return first + second;
}
// 2
console.log(add(1));
在上面这段代码中,参数second的默认值是参数first的值,如果只传入一个参数,则两个参数的值相同,从而add(1)返回的结果为2.
更进一步,可以将第一个参数传入函数来获得参数second的值。就像这样
function getValue(value) {
return value + 5;
}
function add(first, second = getValue(first)) {
return first + second;
}
// 2
console.log(add(1, 1));
// 7
console.log(add(1));
在引用默认值的时候,只允许引用前面参数的值,即先定义的参数不能访问后定义的值。
function add(first = second, second) {
return first + second;
}
// 2
console.log(add(1, 1));
// Uncaught ReferenceError: Cannot access 'second' before initialization
console.log(add(undefined,1));
因为second比first晚定义,因此其不能作为first的默认值,这里涉及到临时死区(TDZ)的概念。
- 默认参数的临界死区问题
在 ES6之块级作用域绑定 当中介绍了临时死区(TDZ),其实默认参数也有同样的临时死区,在临时死区的参数不可访问。与let声明类似,定义参数时会为每个参数创建一个新的标识符绑定,该绑定在初始化之前不可被引用,如果试图访问会导致程序抛出错误。当调用函数时,会通过传入的值或参数的默认值初始化该参数。
比如上一个例子当中
function getValue(value) {
return value + 5;
}
function add(first, second = getValue(first)) {
return first + second;
}
// 2
console.log(add(1, 1));
// 7
console.log(add(1));
调用add(1,1)时实际上相当于执行以下代码来创建first和second参数值。
let first = 1;
let second = 1;
而调用add(1)时实际上相当于执行以下代码来创建first和second参数值
let first = 1;
let second = getValue(first);
当初次执行函数add()时,绑定first和second被添加到一个专属于函数参数的临时死区(与let的行为类似)。由于初始化second时first已经被初始化,所以它可以访问first的值,但是反过来就错了。比如
function add(first = second, second) {
return first + second;
}
// 2
console.log(add(1, 1));
// Uncaught ReferenceError: Cannot access 'second' before initialization
console.log(add(undefined,1));
在add(undefined,1)的时候类似于
let first = second;
let second = 1;
因为first初始化时,second还在临时死区当中,尚未初始化,所以会导致程序抛出错误。
函数参数有自己的作用域和临时死区,其与函数体的作用域时各自独立的,也是说参数的默认值不可访问函数体内声明的变量