在es6中,可以这样来使用函数的参数
function test(a, ...rest) {
console.log(a, rest)
}
test(1, 23, 4, 3)
它支持将剩下的参数以数组的形式放入形参的最后一个参数中。
但是在underScore中,不能这样直接来使用后,因为不确定执行的环境,underScore内部自己实现了一个restArguments方法,就是用es5的方式来实现,可以让我们随心所欲的使用剩余参数语法。
看一下这个函数怎么来使用:
function test(a, rest) {
console.log(a, rest)
}
const restTest = _.restArguments(test);
restTest(1, 2, 3, 4)
restArguments接受一个函数并返回一个函数,返回的函数传递的参数,可以支持剩余参数。看下源码实现:
function restArguments(func, startIndex) {
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
var length = Math.max(arguments.length - startIndex, 0),
rest = Array(length),
index = 0;
for (; 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);
};
}
看起来很复杂,没关系,我们一点一点的来分析:
我们默认使用传入的函数的最后一个参数储存剩余的参数
先来思考一下,如果我要把剩余参数都给func的最后一个参数,那是不是需要找到func的最后一参数,如何拿到呢?
代码中有一个func.length,每一个函数都有一个length属性,返回的是函数形参的个数,注意是形参的个数,不是实参的个数,func.length-1就找到了最后参数所在的位置,用startIndex存下来
function test(a, b, rest) {
console.log(a, rest)
}
console.log(test.length) // 3
如果有剩余参数,会忽略
function test(a, b, ...rest) {
}
console.log(test.length) // 2
test(1)
接下来需要知道实参有几个,也就是调用的过程中到底传递了几个参数,restTest调用这个函数就相当于调用restArguments中return的函数,实参个数可以通过arguments来获取。
就拿上面来举例
arguments.length是4, fun.length是2。那test的length就是3。restArguments函数可以接受两个参数,也就是可以指定开始位置。默认会将用最后一个参数存贮剩余参数,从哪个位置开始。
如果有传递开始位置,就使用传递的,没有传递就使用形参个数减去1。startIndex也可以理解为其它参数的个数。也就是除了rest之外的参数的个数这样会更好理解一点。
startIndex = startIndex == null ? func.length - 1 : +startIndex;
参数的总个数减去其它参数的个数存储在length中,就是剩余参数的长度,和0取最大值,是为了防止出现负值的情况。
var length = Math.max(arguments.length - startIndex, 0),
这种情况下会出现负值, arguments.length是2,fun.length-1是3。
function test(a, b, c, rest) {
console.log(a, rest)
}
const restTest = _.restArguments(test);
restTest(1, 2)
这里就拿到了剩余参数数组:
rest[index] = arguments[index + startIndex];
下面要做的就是把参数传递给fun了,这里先分出了3中情况,使用的call调用,传递参数,最后一个是数组,但是如果剩余参数前面的参数比较多,肯定不能这样一个一个传递。
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)
}
还好有另外一种方式,可以使用apply,传递一个数组就ok了。但是需要把剩余参数(数组)和剩余参数前面的参数放在一个数组里面。下面的代码就做了这样的事情:
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
写到这里,是不是觉得上面的switch有点没有必要,直接走下面的代码不久可以了,干嘛要那么啰嗦。这里是underScore做的一个性能优化,考虑到call的性能比apply的高,所有在这里多做了判断。