一、在ECMAScript5中模拟默认参数
JavaScript函数有一个特别的地方,无论在函数定义中声明了多少参数,都可以传入任意数量的参数,也可以在函数定义时添加针对参数数量的处理逻辑,当已定义的形参无对应的传入参数时,为其指定一个默认值。
在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
function makeRequest(url,timeout,callback){
timeout = timeout||2000;
callback = callback||function(){};
//函数的其余部分
}
在这个例子中,timeout和callback为可选参数,如果不传入相应的参数,系统就会自己给他们赋一个默认值。在含有逻辑或者操作符的表达式中,前一个操作符的值为false时,总会返回后一个值。对于函数的命名参数,如果不显式传值,则其值默认为undefined。因此我们经常用逻辑或者操作符来为缺失的参数提供默认值。然而这个方法是有缺陷的,如果我们想给makeRequest函数的第二个形参timeout传入值0,即使这个值是合法的,也会被视为一个false值,并最终将timeout赋值为2000.
我们可以采取更安全的选择,即通过typeof检查参数类型,如下例所示:
function makeRequest(url,timeout,callback){
timeout = (typeof timeout !== "undefined")? timeout :2000;
callback = (typeof callback !== "undefined")?callback :function(){};
//函数的其余部分
}
尽管这种方法更安全,但是需要额外的代码来执行这种非常基础的操作,这是一种常见的用法,在流行的JavaScript库中很多都采用了这种写法来进行模式补全。
二、在ECMAScript 6中的默认参数值
ECMAScript6简化了为形参提供默认值的过程,如果没为参数传入值则为其提供一个初始值。
如:
function makeRequest(url,timeout = 2000,callback = function(){}){
//the rest
}
在这个例子中,只有第一个参数被认为总是要为其传入值的,其他两个参数都有默认值,而且不需要添加任何校验值是否缺失的代码,所以函数会更简洁。
//使用参数timeout和参数callback的默认值
makeRequest("/foo");
//使用参数callback的默认值
makeRequest("/foo",500);
//不使用默认值
makeRequest("/foo",500,function(params){
doSomething(body);
});
在声明函数时,可以为任意参数指定默认值,在已指定的参数后面就可以继续声明无默认值参数。
function makeRequest(url,timeout = 2000,callback){
//函数的其余部分
}
在上面的例子中,只有当不为第二个参数传入值或者主动为第二个参数传入undefined时才会使用timeout的默认值
//使用timeout的默认值
makeRequest("/foo",undefined,function(){
})
//使用timeout的默认值
makeRequest("/foo");
//不使用timeout的默认值
makeRequest("/foo",null,function(body){
});
对于默认参数值,null是一个合法的值,也就是说第三次调用makeRequest方法的时候,不使用timeout的默认值,其值最终为null
三、 默认参数值对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
当first和second被赋新值时,arguments[0]和arguments[1]也随之改变。
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
在ES6中,如果一个函数使用了默认参数值,则无论是否显式定义了严格模式,arguments对象的行为都将与ES5严格模式下保持一致。
四、默认参数表达式
非原始值传参
可以通过函数执行来得到默认参数的值
function getValue(){
return 5;
}
function add (first ,second = getValue()){
return first + second;
}
console.log(add(1,1));
console.lg(add(1));
在此例中,如果不传入second参数,就会调用getValue()函数来得到正确的默认值。当然,第一次解析函数的时候,不会调用getValue()函数。
注意:如果将getValue()写成getValue,那么最终传入的是对函数的引用,而不是对函数调用的结果。
因为默认参数在函数调用中求值,可以使用先定义的参数作为后定义参数的默认值
function add(first ,second = first){
return first + second ;
}
console.log(add(1,1)); //2
console.log(add(1)); //2
在引用参数默认值时,只允许引用前面参数的值,即先定义的参数才能访问后定义的参数。
function add(first = second ,second ){
return first + second;
}
console.log(add(1,1));//2
console.log((undefined,1)); //抛出错误
第二个调用会抛出错误,因为second比first晚定义,因此其不能作为first的默认值。
默认参数的临时死区
同let和const一样,默认参数也有同样的临时死区,在这里参数不可访问。
function getValue(value){
return value + 5;
}
function add(first,second = getValue(first)){
return first + second ;
}
console.log(add(1,1));
console.log(add(1));
其实调用的过程相当于下面代码
//表示调用add(1,1)时的JavaScript代码
let first = 1;
let second = 1;
//表示调用add(1)时的JavaScript代码
let first = 1;
let second = getValue(first);
当初次执行函数add()时,绑定first和second被添加到一个专属于函数参数的临时死区。由于初始化second时,first已经被初始化,所以它可以访问first的值,但是反过来就错了。
本文介绍了在ES6中引入的函数默认参数值特性,对比了ES5中模拟默认参数的常见方法及其局限性,详细阐述了ES6中如何设置默认参数、默认参数对arguments对象的影响,以及默认参数表达式的使用规则,包括临时死区的概念。
291

被折叠的 条评论
为什么被折叠?



