函数柯里化
定义 #
把接受多个参数的函数转换成接受一个单一参数的函数
curry 的一些性能问题
- 存取
arguments
对象通常要比存取命名参数要慢一点 - 一些老版本的浏览器在
arguments.length
的实现上是相当慢的 - 使用
fn.apply()
和fn.call()
通常比直接调用fn()
稍微慢点 - 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上
简单版本 #
/**
* 将函数柯里化
* @param {Function} func 待柯里化的原函数
* @param {Number} len 所需的参数个数,默认为原函数的形参个数
* @returns {*} 柯里化后的函数
*/
function curry(func, len = func.length) {
return _curry.call(this, func, len);
}
/**
* 柯里化中转函数
* @param {Function} func 待柯里化的原函数
* @param {Number} len 所需的参数个数,默认为原函数的形参个数
* @param {Number} args 已接收的函数列表
* @returns {*} 继续柯里化函数或最终结果
*/
function _curry(func, len, ...args) {
return function (...params) {
const _args = [...args, ...params];
// 接收足够参数后,执行原函数
if (_args.length >= len) {
return func.apply(this, _args);
}
return _curry.call(this, func, len, ..._args);
};
}
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
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
验证一下:
const fn = curry(function (a, b, c, d, e) {
console.log(a, b, c, d, e);
});
fn(1, 2, 3, 4, 5); // 1 2 3 4 5
fn(1)(2)(3, 4, 5); // 1 2 3 4 5
fn(1, 2)(3, 4)(5); // 1 2 3 4 5
fn(1)(2)(3)(4)(5); // 1 2 3 4 5
1
2
3
4
5
6
7
2
3
4
5
6
7
不借助中转函数 #
/**
* 将函数柯里化
* @param {Function} func 待柯里化的原函数
* @param {Number} len 所需的参数个数,默认为原函数的形参个数
* @returns {*} 柯里化后的函数
*/
function curry(func, len = func.length) {
const args1 = Array.prototype.slice.call(arguments, 2);
return function () {
const args2 = Array.prototype.slice.call(arguments);
const args = [].concat(args1, args2);
if (args.length >= len) {
return func.apply(this, args);
}
return curry.call(this, func, len, ...args);
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
验证一下:
const fn = curry(function (a, b, c, d, e) {
console.log(a, b, c, d, e);
});
fn(1, 2, 3, 4, 5); // 1 2 3 4 5
fn(1)(2)(3, 4, 5); // 1 2 3 4 5
fn(1, 2)(3, 4)(5); // 1 2 3 4 5
fn(1)(2)(3)(4)(5); // 1 2 3 4 5
1
2
3
4
5
6
7
2
3
4
5
6
7
占位符版本 #
/**
* 将函数柯里化
* @param {Function} func 待柯里化的原函数
* @param {Number} len 所需的参数个数,默认为原函数的形参个数
* @param {*} holder 占位符,默认为当前柯里化函数
* @returns {*} 柯里化后的函数
*/
function curry(func, len = func.length, holder = curry) {
return _curry.call(this, func, len, holder, [], []);
}
/**
* 柯里化中转函数
* @param {Function} func 待柯里化的原函数
* @param {Number} len 所需的参数个数,默认为原函数的形参个数
* @param {Number} holder 接收的占位符
* @param {Number} args 已接收的函数列表
* @param {Number} holders 已接收的占位符位置列表
* @returns {*} 继续柯里化函数或最终结果
*/
function _curry(func, len, holder, args, holders) {
return function (...params) {
const _args = [...args]; // 将参数复制一份,避免多次操作同一函数导致参数混乱
const _holders = [...holders]; // 将占位符位置列表复制一份,新增加的占位符增加到此
// 遍历接收到的参数列表
params.forEach((arg) => {
// 如果当前的这个参数是正常参数
if (arg !== holder) {
// 如果原占位符位置列表中不存在占位符,直接追加到参数列表当中
if (!holders.length) {
_args.push(arg);
}
// 如果原占位符位置列表中存在占位符,则替换掉参数列表里的占位符
else {
const index = holders.shift(); // 获取之前还未被替换的占位符位置
// 删除替换的占位符,这里 _holders 可能会存在新的占位符,所以不能删除第一个元素
_holders.splice(_holders.indexOf(index), 1);
_args[index] = arg;
}
}
// 如果当前的这个参数是占位符
else {
// 如果原占位符位置列表中不存在占位符,记录占位符的位置
if (!holders.length) {
_args.push(arg);
_holders.push(_args.length - 1);
}
// 如果原占位符位置列表中存在占位符,则删除原占位符位置列表中的占位
else {
holders.shift();
}
}
});
// 接收到指定数量的参数后,并且接收到的指定数量的参数中不存在占位符,则执行原函数
if (_args.length >= len && _args.slice(0, len).every((v) => v !== holder)) {
return func.apply(this, _args);
}
return _curry.call(this, func, len, holder, _args, _holders);
};
}
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
验证一下:
const fn = (a, b, c, d, e) => {
console.log(a, b, c, d, e);
};
const _ = {}; // 定义占位符
const _fn = curry(fn, fn.length, _);
_fn(1, 2, 3, 4, 5); // 1 2 3 4 5
_fn(_, 2, 3, 4, 5)(1); // 1 2 3 4 5
_fn(1, _, 3, 4, 5)(2); // 1 2 3 4 5
_fn(1, _, 3)(_, 4, _)(2)(5); // 1 2 3 4 5
_fn(1, _, _, 4)(_, 3)(2)(5); // 1 2 3 4 5
_fn(_, 2)(_, _, 4)(1)(3)(5); // 1 2 3 4 5
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
编辑 (opens new window)
上次更新: 2021-06-23 08:43:55