柯里化

概念

将一个函数拆分成多个函数


示例

1
2
3
4
5
6
// 判断类型 Object.prototype.toString.call
const checkType = (content, type) =>{
return Object.prototype.toString.call(content) === `[object ${type}]`;
}
let b = checkType(123, 'Number');
console.log(b); // true

示例拓展:

1
2
3
let b = checkType(123, 'Number');
// 当用户传入向 'Number'这样的会有传入拼写错误的情况
// 如果用户传入的 'Number'错误,那这个方法就会返回false

所以现在想要 类似于 isString(‘12345’)这样实现,因为如果方法名写错会报错,而‘Number’这个字符串写错 结果就是false了

1
2
3
4
5
6
7
8
const checkType = (type) =>{
return (content) => {
return Object.prototype.toString.call(content) === `[object ${type}]`;
}
}
const isString = checkType('String');
let res = isString('1234')
console.log(res); // true

如上line1-5将一个函数(checkType )拆分成了2个更细小的部分
当执行line6时 传过去的‘String’ 保存到了line2-4这个函数的作用域中

1
2
3
(content) => {
return Object.prototype.toString.call(content) === `[object ${type}]`;
}

它返回的函数 可以在其他作用域(即line7) 中执行 即为闭包,函数checkType返回的值可以在其他作用域中执行

面试题

函数柯里化怎么实现

1
add(1,2)(3)(4)(5); // 15

这里可以构建add函数如下:

1
2
3
const add = (a, b, c, d, e) => {
return a + b + c + d + e;
}

分析:当调用的参数个数为5 == 该函数本身参数个数的时候才执行了return 所以app(1,2)时返回一个函数,app(3)时返回一个函数…
每次调用的时候存值[1,2,3,4,5]当这个数组中的个数和这个方法中的参数个数相等时执行。
完整代码如下:

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
const add = (a, b, c, d, e) => {
return a + b + c + d + e;
}
const curring = (fn, arr = []) => {
let len = fn.length; // 函数的长度是参数的个数 所以这里的len代表函数add的参数
return (...args) => {
arr = arr.concat(args);
if(arr.length < len){
return curring(fn, arr);
}
return fn(...arr);
}
}
let curry = currying(add)(1,2)(3)(4)(5);
console.log(curry); // 15

// 自己实现
const add = (a,b,c,d,e) =>{
return a + b+ c+ d +e;
}

const curring = (fn, arr = []) =>{
return function (){
arr.push(...arguments);
if(arr.length === fn.length){
return fn(...arr);
}
return curring(fn, arr);
}
}
let _add = curring(add);
console.log(_add(1,2)(3)(4)(5)); // 15

以上为通用柯里化,好处是可以把函数更细化。
利用闭包将函数的参数储存起来,等参数达到一定数量时执行函数。

示例拓展优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 用checkType实现
const curring = (fn, arr = []) => {
let len = fn.length;
return (...args) => {
arr = arr.concat(args);
if(arr.length < len){
return curring(fn, arr);
}
return fn(...arr);
}
}
const checkType = (type, content) =>{
return Object.prototype.toString.call(content) === `[object ${type}]`;
}
let types = ['String', 'Number'];
let utils = {};
types.forEach( type => {
utils["is" + type] = curring(checkType)(type);
})
console.log(utils.isString('123')); // true
// 这个调用 执行的是line4-10区域
console.log(curring(checkType)('String')('123')); // true
// 当前行的执行 其实就是line17-20,只是18行先将类型传了过去与上面的内容一样 不让用户传类似‘String’,而是传isString()函数,这样直接调用。

柯里化的作用

参数复用

1
2
3
4
5
6
7
function volume(length, width, height) {
return length * width * height;
}
volume(200, 100, 200);
volume(200, 150, 100);
volume(200, 50, 80);
volume(100, 50, 60);

如上计算长方体的体积函数会发现存在很多相同长度的长方体,我们再用柯里化函数实现一下:

1
2
3
4
5
6
7
8
9
10
11
12
function volume(length, width, height) {
return function(width) {
return function(height) {
return length * width * height;
}
}
}
let len200 = volume(200);
len200(100)(200);
len200(150)(100);
len200(50)(80);
volume(100)(50)(60);

如上,通过实现一个len200函数我们统一处理长度为200的长方体的体积,这就实现了参数复用

[场景] https://segmentfault.com/a/1190000010878974

Curry 的核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。