函数科里化

函数科里化

把一个需要传入多个参数的函数,变成多个嵌套只需传入一个参数的函数,并且返回接受余下参数而且返回结果的新参数

  • 用于创建已经设置好了一个或多个参数的函数
  • 科里化和函数绑定类似:使用一个闭包返回一个函数
  • 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
  • 基本思想:调用另一个函数,并为它传入科里化的函数和必要参数

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const curry = (fn, ...arg) => {
let all = arg || [];
function cb(...rest) {
if (rest.length === 0) {
return fn.apply(null, all) // this
}
Array.prototype.push.apply(all, [].slice.call(rest))
return cb
}
return cb
}

function add(...args) {
return args.reduce((x, y) => {
return x + y
})
}
console.log(curry(add, 2)(8)())

最简单方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function curry(fn){
const args= Array.prototype.slice.call(arguments,1)
return function(){
const innerArgs= Array.prototype.slice.call(arguments)
const finalArgs= args.concat(innerArgs)
return fn.apply(null, finalArgs)
}
}


// 包含函数绑定
function bind(fn,context){
const args= Array.prototype.slice.call(arguments,2)
return function(){
const innerArgs= Array.prototype.slice.call(arguments)
const finalArgs= args.concat(innerArgs)
return fn.apply(context, finalArgs)
}
}

使用场景

  • 参数服用
  • 减少重复传递不变的部分参数(固定易变元素)
  • 延迟执行
  • 提高适用性
  1. 参数复用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}

check(/\d+/g, 'test') //false
check(/[a-z]+/g, 'test') //true

// Currying后
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}

var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
  1. 实现一个sum
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
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);

// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function() {
_args.push(...arguments);
return _adder;
};

// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}

add(1)(2)(3) // 6
add(1, 2, 3)(4) // 10
add(1)(2)(3)(4)(5) // 15
add(2, 6)(1) // 9

过程

  1. 普通返回函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function add(a, b) {
    return a + b;
    }

    // 函数只能传一个参数时候实现加法
    function curry(a) {
    return function (b) {
    return a + b;
    }
    }
    curry(2)(3)
  2. 不定参

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const curry = (fn, ...arg) => {
    let all = arg;
    return (...rest) => {
    all.push(...rest);
    return fn.apply(null, all);
    }
    }
    curry(add, 2)(8)
    curry(add)(2, 8)
  3. 给函数执行绑定执行环境也很简单,可以多传入个参数

1
2
3
4
5
6
7
const curry = (fn, constext, ...arg) => {
let all = arg;
return (...rest) => {
all.push(...rest);
return fn.apply(constext, all);
}
}
  1. 判断参数是否已经达到预期的值(函数柯里化之前的参数个数),如果没有继续返回函数,达到了就执行函数然后返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const curry = (fn, ...arg) => {
let all = arg || []
let length = fn.length
return (...rest) => {
let _args = all.slice(0)
//拷贝新的all,避免改动公有的all属性,导致多次调用_args.length出错
_args.push(...rest)
if (_args.length < length) {
return curry.call(this, fn, ..._args)
} else {
return fn.apply(this, _args)
}
}
}

let test = curry(function (a, b, c) {
console.log(a + b + c)
})
test(1, 2, 3)
test(1, 2)(3)
test(1)(2)(3)
  1. 至于在什么时候执行返回值,控制权在我们,设置的传入参数为空时候触发函数执行返回值(具体实现)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const curry = (fn, ...arg) => {
let all = arg || []
let length = fn.length
return (...rest) => {
let _args = all.slice(0)
_args.push(...rest)

if (rest.length === 0) {
all = []
return fn.apply(this, _args)
} else {
return curry.call(this, fn, ..._args)
}
}
}
let test = curry(function (...rest) {
let args = rest.reduce((x, y) => {
return x + y
})
console.log(args)
})
test(2, 3, 4)()