研究下JavaScript中的Rest參数和參数默认值
本文将讨论使 JavaScript 函数更有表现力的两个特性:Rest 參数和參数默认值。
Rest 參数
通常,我们须要创建一个可变參数的函数,可变參数是指函数能够接受随意数量的參数。比如,String.prototype.concat 能够接受不论什么数量的字符串作为參数。使用 Rest 參数,ES6 为我们提供一种新的方式来创建可变參数的函数。
我们来实现一个演示样例函数 containsAll。用于检查一个字符串中是否包括某些子字符串。比如,containsAll("banana", "b", "nan") 将返回true。containsAll("banana", "c", "nan") 将返回 false。
以下是传统的实现方式(參考: http://www.aichengxu.com/view/62895):
function containsAll(haystack) { for (var i = 1; i < arguments.length; i++) { var needle = arguments[i]; if (haystack.indexOf(needle) === -1) { return false; } } return true; } function containsAll(haystack) { for (var i = 1; i < arguments.length; i++) { var needle = arguments[i]; if (haystack.indexOf(needle) === -1) { return false; } } return true; }
该实现用到了 arguments 对象,该对象是一个类数组对象,包括函数被调用时的实參列表。
这段代码正是我们想要的。但其可读性却不是最优的。
函数仅仅有一个形參 haystack,所以不可能一看就知道该函数须要多个參数。而且在遍历 arguments 时。须要特别注意遍历的開始索引为1 。而不是常见的 0,由于 arguments[0] 就是函数定义时的形參 haystack。假设我们想在 haystack 參数之前或之后加入一些參数,我们不得不更新内部的循环。Rest 參数攻克了这些问题,以下是 使用 Rest
參数的实现方式:
//http://www.aichengxu.com function containsAll(haystack, ...needles) { for (var needle of needles) { if (haystack.indexOf(needle) === -1) { return false; } } return true; } function containsAll(haystack, ...needles) { for (var needle of needles) { if (haystack.indexOf(needle) === -1) { return false; } } return true; }
以上两个实现都满足了我们的需求,但后者包括一个特殊的 ...needles 语法。我们来看看调用containsAll("banana", "b", "nan") 时的细节,參数 haystack 和以往一样,将用函数的第一个实參填充,值为 "banana"。needles 前面的省略号表示它是一个 Rest 參数。剩余的全部实參将被放入一个数组中。并将该数组赋给 needles 遍量。在这个调用中。needles 的值为 ["b", "nan"]。然后,就是正常的函数运行了。
仅仅能将函数的最后一个函数作为 Rest 參数,在函数被调用时。Rest 參数之前的參数都将被正常填充,之外的參数将被放入一个数组中,并将该数组作为 Rest 參数的值,假设没有很多其它的參数。那么 Rest 參数的值为一个空数组 [],Rest 參数的值永远都不会是 undefined。
參数的默认值
通常,调用一个函数时,不须要调用者传递全部可能的參数,那些没有传递的參数都须要一个合理的默认值。
JavaScript 对那些没有传递的參数都有一个固定的默认值 undefined。
在 ES6 中,引入了一种新方法来指定随意參数的默认值。
看以下样例:
function animalSentence(animals2="tigers", animals3="bears") { return `Lions and ${animals2} and ${animals3}! Oh my!`; } function animalSentence(animals2="tigers", animals3="bears") { return `Lions and ${animals2} and ${animals3}! Oh my!`; }
在每一个參数的 = 后面是一个表达式。指定了參数未传递时的默认值。所以,animalSentence() 返回 "Lions and tigers and bears! Oh my!", animalSentence("elephants") 返回"Lions and elephants and bears! Oh my!", animalSentence("elephants", "whales") 返回 "Lions and elephants and whales! Oh my!"。
參数默认值须要注意的几个细节:
与 Python 不一样的是。參数默认值的表达式是在函数调用时从左到右计算的。这意味着表达式能够使用前面已经被填充的參数。比如,我们能够将上面的函数变得更有趣一点:
function animalSentenceFancy(animals2="tigers", animals3=(animals2 == "bears") ?"sealions" : "bears") { return `Lions and ${animals2} and ${animals3}! Oh my!`; } function animalSentenceFancy(animals2="tigers", animals3=(animals2 == "bears") ?
"sealions" : "bears") { return `Lions and ${animals2} and ${animals3}! Oh my!`; }
那么,animalSentenceFancy("bears") 将返回 "Lions and bears and sealions. Oh my!"。
传递 undefined 等同于没有传递该參数。因此,animalSentence(undefined, "unicorns") 将返回 "Lions and tigers and unicorns! Oh my!"。
假设没有为一个參数指定默认值,那么该參数的默认值为 undefined,所以
function myFunc(a=42, b) {...} function myFunc(a=42, b) {...}
等同于
function myFunc(a=42, b=undefined) {...} function myFunc(a=42, b=undefined) {...}
抛弃 arguments
通过 Rest 參数和參数的默认值,我们能够全然抛弃 arguments 对象。使我们的代码可读性更高。此外,arguments 对象也加深了优化 JavaScript 的难题。
希望以上两个新特性能够全然代替 arguments。作为第一步,在使用 Rest 參数或參数的默认值时,请避免使用 arguments 对象,假如 arguments 对象还不会马上被移除,或者永远不会。那么也最好是避免在使用 Rest 參数或參数默认值时使用 arguments 对象。
兼容性
Firefox 15 以上的版本号已经支持这两个新特性。然而,除此之外,还没有其它不论什么浏览器支持。近期,V8 的实验环境加入了对 Rest 參数的支持,而參数默认值另一个 issue。JSC 也对 Rest 參数和參数默认值提了一些 issue。
Babel 和 Traceur 这两个编译器都已经支持了參数默认值。所以你能够大胆使用。
结论
虽然从技术层面上看,这两个新特性在并没有给函数引入新的行为,但它们能够使一些函数的声明更具表现力和可读性。