ES6中的Rest Parameters语法
在ES6中规定了Rest Parameters语法:
var f = function(a, b, ...theArgs) {
...
}
f(1, 2, 3, 4, 5) // a=1, b=2, theArgs=[3, 4, 5]
请注意f的第三个参数,在声明时以'...'开头。这样在实际调用时,函数的前两个参数分别映射成a、b,从第三个参数开始,这些参数按照顺序映射成名为theArgs的数组。
这样当函数的参数数量不确定时,我们不需要通过arguments对象来确定函数的参数,使函数写起来更加自然。
rest parameters和arguments对象有以下主要的区别:
- rest parameters只包含没有特别命名的函数参数,比如f中的除了a,b以外的函数参数;arguments对象中包含所有的函数参数
- arguments对象并不是一个真正的array,然而rest parameters却是一个货真价实的Array实例,可以直接使用sort、map、forEach、push、pop等函数
- arguments拥有其他特殊技能(比如callee property)
更多细节请看:MDN中关于rest parameters的介绍
underscore中模拟Rest Parameters语法
在underscore有这样一个内部函数restArgs,直接看代码:
var restArgs = function(func, startIndex) {
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
...
}
};
这个函数可以把一个函数func的参数"改造"成Rest Parameters,如果不传第二个参数startIndex,默认用最后一个参数收集其余参数。比如_.invoke函数
_.invoke = restArgs(function(obj, method, args) {
var isFunc = _.isFunction(method);
return _.map(obj, function(value) {
var func = isFunc ? method : value[method];
return func == null ? func : func.apply(value, args);
});
});
在初始化_.invoke时就使用了restArgs函数,并且没有传startIndex参数。这样匿名函数function(obj, method, args) {...}的第三个参数args就负责来收集"剩余参数"。
_.invoke函数是将obj遍历,每一项执行一个指定方法method,由于method的不确定性,给method传的参数也具有不确定性。用args直接收集成一个数组,直接使用func.apply(value, args)就可以直接调用函数,十分方便。
我们再来看下restArgs函数的源码
var restArgs = function(func, startIndex) {
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
var length = Math.max(arguments.length - startIndex, 0);
var rest = Array(length);
for (var index = 0; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
switch (startIndex) {
case 0: return func.call(this, rest);
case 1: return func.call(this, arguments[0], rest);
case 2: return func.call(this, arguments[0], arguments[1], rest);
}
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
};
};
其实也很简单,startIndex代表从第几个参数开始当作是剩余参数,第一步首先修正startIndex,如果没有传,startIndex就是undefined,将startIndex修正为func.length - 1 (一个函数的length属性是这个函数在声明时声明的形参个数)
然后从startIndex开始遍历arguments对象,将startIndex之后的参数放入rest数组中。
接下来就可以调用func函数了,当startIndex为0,1,2时,使用了call方法。由于call方法要将参数一个个传入,当startIndex为其他值(可能很大时),作者可能发现一个个写起来很傻,就用了apply函数(真实是为了性能考虑)。
在underscore中,通过_.restArgs = restArgs将这个函数直接输出,我们可以直接使用。在underscore内部有以下函数使用了restArgs初始化:
- _.invoke
- _.without
- _.union
- _.difference
- _.zip
- _.bind
- _.partial
- _.bindAll
- _.delay
- _.debounce
- _.pick
- _.omit