JavaScript学习之浅谈函数柯里化(Currying)

前言

最近是想着坚实一下自己JS的基础,所以想着看看原生的知识。

准备

在这里先说说什么是函数柯里化。

  • 什么是函数柯里化

    在一个函数中首先填充几个参数(然后再返回一个新函数)的技术称为柯里化(Currying)。(来自《JavaScript忍者秘籍》一书中)

开始

这里有两个例子可以帮助理解!
最简单的加法函数

1
2
3
4
5
6
//函数定义
function add(x,y){
return x + y;
}
//函数调用
add(3,4);//5

如果采用柯里化是怎样将接受两个参数的函数变成接受单一参数的函数呢,其实很简单如下:

1
2
3
4
5
6
7
8
//函数表达式定义
var add = function(x){
return function(y){
return x + y;
}
};
//函数调用
add(3)(4);

其实实质利用的就是闭包的概念。

作用

本质上讲柯里化(Currying)只是一个理论模型,柯里化所要表达是:如果你固定某些参数,你将得到接受余下参数的一个函数,所以对于有两个变量的函数y^x,如果固定了y=2,则得到有一个变量的函数2^x。这就是求值策略中的部分求值策略。
柯里化(Currying)具有:延迟计算、参数复用、动态生成函数的作用。

JS中bind()与函数柯里化

其实在实际使用中使用最多的一个柯里化的例子就是Function.prototype.bind()函数,我们也一并给出一个较为简单的Function.prototype.bind()函数的实现方式。

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
//《JS权威指南》原文的例子
var sum = function(x,y) { return x + y };

var succ = sum.bind(null, 1); //让this指向null,其后的实参也会作为实参传入被绑定的函数sum

succ(2); // => 3: 可以看到1绑定到了sum函数中的x

//传入bind中的实参都会绑定到原函数的形参
function func(a,b,c,d){...} //func的length为4

var after = func.bind(null,1,2); //这里输入了两个实参(1,2)绑定到了func函数的a,b

console.log(after.length); //after的length为2

//当bind()所返回的函数用作构造函数的时候, 传入bind()的this将被忽略,实参会全部传入原函数
function original(x){
this.a = 1;
this.b = function(){return this.a + x}
}
var obj={
a = 10
}
var newObj = new(original.bind(obj, 2)); //传入了一个实参2

console.log(newObj.a); //输出1, 说明返回的函数用作构造函数时obj(this的值)被忽略了
console.log(newObj.b()); //输出3 ,说明传入的实参2传入了原函数original


简单的`Function.prototype.bind()`函数的实现方式。
Function.prototype.bind = function(){
var fn = this;
var args = Array.prototye.slice.call(arguments);
var context = args.shift();

return function(){
return fn.apply(context,
args.concat(Array.prototype.slice.call(arguments)));
};
};

实战演练一道关于闭包和函数的柯里化方面的编程题目

编程题目的要求如下,完成plus函数,通过全部的测试用例。

1
2
3
4
5
'use strict';
function plus(n){

}
module.exports = plus

测试用例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'use strict';
var assert = require('assert')

var plus = require('../lib/assign-4')

describe('闭包应用',function(){
it('plus(0) === 0',function(){
assert.equal(0,plus(0).toString())
})
it('plus(1)(1)(2)(3)(5) === 12',function(){
assert.equal(12,plus(1)(1)(2)(3)(5).toString())
})
it('plus(1)(4)(2)(3) === 10',function(){
assert.equal(10,plus(1)(4)(2)(3).toString())
})
it('方法引用',function(){
var plus2 = plus(1)(1)
assert.equal(12,plus2(1)(4)(2)(3).toString())
})
})

根据测试用例我们可以发现,plus函数的要求就是接受单一函数,例如:

1
plus(1)(4)(2)(3).toString()

但是与柯里化不同之处在于,柯里化返回的一个新函数。我们观察其实最后的求值是通过toString函数得到的,那么我们就很容易想到,我们可以给返回的函数增加一个toString属性就可以了。答案如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'use strict';

function plus(num) {
var adder = function () {
var _args = [];
var _adder = function _adder() {
[].push.apply(_args, [].slice.call(arguments));
return _adder;
};

_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}

return _adder;
}
return adder()(num);
}

module.exports = plus;

运行一下,通过全部的测试用例,需要注意的是由于题目的要求运行在严格模式下,所以我们在_adder函数内部是不能引用arguments.callee,这时我们采用的方法是给函数表达式中函数本身起名_adder,这样就解决的这个问题。

本文标题:JavaScript学习之浅谈函数柯里化(Currying)

文章作者:NoHomeMan

发布时间:2018年09月18日 - 14:09

最后更新:2018年09月18日 - 15:09

原始链接:http://nohomeman.github.io/JavaScript学习之浅谈函数柯里化-Currying/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------The End-------------