underscore之内部函数restArgs

原文来至冴羽

partial

用来固定函数的部分参数

1
2
3
4
5
6
7
8
function partial(fn) {
var args = [].slice.call(argumetns, 1);
return function(){
var newArgs = args.concat([].slice.call(arguments));

return fn.apply(this, newArgs);
};
};

rest parameter

es6 为我们提供了剩余参数(rest parameter)语法,允许我们将一个不定数量的参数表示为一个数组。

1
2
3
4
function fn(a, b, ...rest) {
console.log(rest); // [3,4,5]
}
fn(1,2,3,4,5)

通过这一特性我们可以简化 partial实现的代码:

1
2
3
4
5
6
function partial(fn, ...args){
return function(...partialArgs) {
var newArgs = args.concat(partialArgs);
return fn.apply(this, newArgs);
};
};

写个demo测试一下

1
2
3
4
5
function add(a, b) {
return a + b;
}
var addOne = partial(add, 1);
console.log(addOne(2)) // 3

restArgs

如果不使用 … 拓展运算符,仅使用es5的内容,该怎么实现呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 第一版
function restArgs(func) {
return function(){
// startIndex 表示使用哪个位置的参数用于储存剩余的参数
var startIndex = func.length - 1;
var length = arguments.length - startIndex;

var rest = Array(length)
var index = 0;

// 使用一个数组储存剩余的参数
// 以上面的例子为例,结果为:
// rest [3, 4, 5]
for (; index < length; index++) {
rest[index] = arguments[index + startIndex]
}

// args [1, 2, undefined]
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index]
}

// args [1, 2, [3, 4, 5]]
args[startIndex] = rest;

return func.apply(this, args)
}
}

优化

我们默认使用传入的函数的最后一个参数存储剩余的参数,为了更加灵活,我们可以再增加一个参数,用来指定startIndex,如果没有指定,就默认使用最后一个参数.

此外,我们使用Array(length)创建数组,而length的计算方式是arguments.length - startIndex,这个值可能是负数!比如:

1
2
3
4
5
var func = restArgs(function(a, b, c, d) {
console.log(c) // 报错;
})

func(1,2)

所以我们再写一版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function restArgs(func, startIndex) {
// 默认使用传入的最后一个参数存储剩余的参数
startIndex = startIndex == null ? func.length - 1 : startIndex;
return function() {
// 处理length可能会小于0的情况
var length = Math.max(arguments.length - startIndex, 0);
var rest = Array(length);
var index = 0;
for(; index < length; index++){
rest[index] = arguments[index + startIndex];
}

var args = Array(startIndex + 1);
for(index = 0; index < startIndex; index++){
args[index] = arguments[index];
}

args[startIndex] = rest;
return func.apply(this, args);
}
}

性能优化

如果是正常写业务,可能到这里就结束了,然而 underscore考虑的更多,鉴于call的性能要高于apply,所以underscore做了一个优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var restArgs = function(func, startIndex) {
startIndex = startIndex == null ? func.length -1 : stawrtIndex;
return function() {
var length = Math.max(arguments.length - startIndex, 0),
index = 0,
rest = Array(length);

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);
}
}

至此,restArgs函数就完成了,underscore很多函数比如 invoke、without、union、difference、bind、partial、bindAll、delay都用到了restArgs函数
在underscore中,我们通过 _.underscore的形式调用该函数

restArgs 与 partial

最后,我们用restArgs函数重写一下 partial函数:

1
2
3
4
5
6
7
8
9
var partial = restArgs(function(fn, args) {

return restArgs(function(partialArgs) {

var newArgs = args.concat(partialArgs);

return fn.apply(this, newArgs);
})
})