深入学习JavaScript中的Rest参数和参数默认值
本文将讨论使JavaScript函数更有表现力的两个特性:Rest参数和参数默认值。
Rest参数
通常,我们需要创建一个可变参数的函数,可变参数是指函数可以接受任意数量的参数。例如,String.prototype.concat可以接受任何数量的字符串作为参数。使用Rest参数,ES6为我们提供一种新的方式来创建可变参数的函数。
我们来实现一个示例函数containsAll,用于检查一个字符串中是否包含某些子字符串。例如,containsAll("banana","b","nan")将返回true,containsAll("banana","c","nan")将返回false。
下面是传统的实现方式:
functioncontainsAll(haystack){ for(vari=1;i<arguments.length;i++){ varneedle=arguments[i]; if(haystack.indexOf(needle)===-1){ returnfalse; } } returntrue; } functioncontainsAll(haystack){ for(vari=1;i<arguments.length;i++){ varneedle=arguments[i]; if(haystack.indexOf(needle)===-1){ returnfalse; } } returntrue; }
该实现用到了arguments对象,该对象是一个类数组对象,包含函数被调用时的实参列表。这段代码正是我们想要的,但其可读性却不是最优的。函数只有一个形参haystack,所以不可能一看就知道该函数需要多个参数,并且在遍历arguments时,需要特别注意遍历的开始索引为1,而不是常见的0,因为arguments[0]就是函数定义时的形参haystack。如果我们想在haystack参数之前或之后添加一些参数,我们不得不更新内部的循环。Rest参数解决了这些问题,下面是使用Rest参数的实现方式:
functioncontainsAll(haystack,...needles){ for(varneedleofneedles){ if(haystack.indexOf(needle)===-1){ returnfalse; } } returntrue; } functioncontainsAll(haystack,...needles){ for(varneedleofneedles){ if(haystack.indexOf(needle)===-1){ returnfalse; } } returntrue; }
以上两个实现都满足了我们的需求,但后者包含一个特殊的...needles语法。我们来看看调用containsAll("banana","b","nan")时的细节,参数haystack和以往一样,将用函数的第一个实参填充,值为"banana",needles前面的省略号表示它是一个Rest参数,剩余的所有实参将被放入一个数组中,并将该数组赋给needles遍量。在这个调用中,needles的值为["b","nan"]。然后,就是正常的函数执行了。
只能将函数的最后一个函数作为Rest参数,在函数被调用时,Rest参数之前的参数都将被正常填充,之外的参数将被放入一个数组中,并将该数组作为Rest参数的值,如果没有更多的参数,那么Rest参数的值为一个空数组[],Rest参数的值永远都不会是undefined。
参数的默认值
通常,调用一个函数时,不需要调用者传递所有可能的参数,那些没有传递的参数都需要一个合理的默认值。JavaScript对那些没有传递的参数都有一个固定的默认值undefined。在ES6中,引入了一种新方法来指定任意参数的默认值。
看下面例子:
functionanimalSentence(animals2="tigers",animals3="bears"){ return`Lionsand${animals2}and${animals3}!Ohmy!`; } functionanimalSentence(animals2="tigers",animals3="bears"){ return`Lionsand${animals2}and${animals3}!Ohmy!`; }
在每个参数的=后面是一个表达式,指定了参数未传递时的默认值。所以,animalSentence()返回"Lionsandtigersandbears!Ohmy!",animalSentence("elephants")返回"Lionsandelephantsandbears!Ohmy!",animalSentence("elephants","whales")返回"Lionsandelephantsandwhales!Ohmy!"。
参数默认值需要注意的几个细节:
与Python不一样的是,参数默认值的表达式是在函数调用时从左到右计算的,这意味着表达式可以使用前面已经被填充的参数。例如,我们可以将上面的函数变得更有趣一点:
functionanimalSentenceFancy(animals2="tigers", animals3=(animals2=="bears")?"sealions":"bears") { return`Lionsand${animals2}and${animals3}!Ohmy!`; } functionanimalSentenceFancy(animals2="tigers", animals3=(animals2=="bears")?"sealions":"bears") { return`Lionsand${animals2}and${animals3}!Ohmy!`; }
那么,animalSentenceFancy("bears")将返回"Lionsandbearsandsealions.Ohmy!"。
传递undefined等同于没有传递该参数。因此,animalSentence(undefined,"unicorns")将返回"Lionsandtigersandunicorns!Ohmy!"。
如果没有为一个参数指定默认值,那么该参数的默认值为undefined,所以
functionmyFunc(a=42,b){...} functionmyFunc(a=42,b){...}
等同于
functionmyFunc(a=42,b=undefined){...} functionmyFunc(a=42,b=undefined){...}
抛弃arguments
通过Rest参数和参数的默认值,我们可以完全抛弃arguments对象,使我们的代码可读性更高。此外,arguments对象也加深了优化JavaScript的难题。
希望以上两个新特性可以完全取代arguments。作为第一步,在使用Rest参数或参数的默认值时,请避免使用arguments对象,假如arguments对象还不会立即被移除,或者永远不会,那么也最好是避免在使用Rest参数或参数默认值时使用arguments对象。
兼容性
Firefox15以上的版本已经支持这两个新特性。然而,除此之外,还没有其他任何浏览器支持。最近,V8的实验环境添加了对Rest参数的支持,而参数默认值还有一个issue,JSC也对Rest参数和参数默认值提了一些issue。
Babel和Traceur这两个编译器都已经支持了参数默认值,所以你可以大胆使用。
结论
尽管从技术层面上看,这两个新特性在并没有给函数引入新的行为,但它们可以使一些函数的声明更具表现力和可读性。