在闭包函数的应用当中,有一个重要的场景就是柯里化。
1. 定义
柯里化是指这样一个函数(假设叫做createCurry
),他接收函数A
作为参数,运行后能够返回一个新的函数。并且这个新的函数能够处理函数A
的剩余参数。
假如有一个接收三个参数的函数A
:
function A(a, b, c) {
// TODO something
}
又假如我们有一个已经封装好了的柯里化通用函数createCurry
。他接收bar
作为参数,能够将A
转化为柯里化函数,返回结果就是这个被转化之后的函数。
var _A = createCurry(A);
那么_A
作为createCurry
运行的返回函数,他能够处理A
的剩余参数。因此下面的运行结果都是等价的。
_A(1, 2, 3);
_A(1, 2)(3);
_A(1)(2, 3);
_A(1)(2)(3);
A(1, 2, 3);
函数A
被createCurry
转化之后得到柯里化函数_A
,_A
能够处理A
的所有剩余参数。因此柯里化也被称为部分求值。
在简单的场景下,我们可以不用借助柯里化通用式来转化得到柯里化函数,我们可以凭借眼力自己封装。
例如有一个简单的加法函数,他能够将自身的两个参数加起来并返回计算结果。
function add(x,y) {
return x + y;
}
那么add
函数的柯里化函数_add
则可以如下:
function _add(x) {
return function (y) {
return x + y
}
}
因此下面的运算方式是等价的。
add(1, 2);
_add(1)(2);
2. 柯里化函数封装
首先通过_add
可以看出,柯里化函数的运行过程其实是一个参数的收集过程,我们将每一次传入的参数收集起来,并在最里层里面处理。因此我们在实现createCurry
时,可以借助这个思路来进行封装。
//ES5实现
function curry(fn) {
var args = [], //装总的参数的数组
n = fn.length; //传入的函数的参数个数
return function core() {
var arg = [].slice.call(arguments); //将任意的类数组对象转化为数组,返回的函数传入的参数
args = args.concat(arg); //收集传入的参数
n -= arg.length; //n的值在不断递归传入参数的过程中逐渐减小,用于判断是否继续递归core
return n===0 ? fn.apply(null, args) : core;
}
}
var add4 = curry(function (a, b, c, d) {
return a + b + c + d;
});
console.log(add4(1, 2)(2)(3)); //8
在ES6
中使用展开运算符"...
"可以不用手动指定this
的绑定,还有箭头函数的特性自行了解,简化了封装的复杂性:
//ES6实现
const curry = (fn, ...args) =>
args.length < fn.length
// 参数长度不足时 重新柯里化该函数 等待接受新的参数
? (...arguments) => curry(fn, ...args, ...arguments)
// 参数长度满足时 执行函数
: fn(...args);
function sumFn(a, b, c) {
return a + b + c;
}
// 举例
console.log(curry(sumFn, 7, 8, 6)) // 21
console.log(curry(sumFn, 7, 8)(6)) // 21
console.log(curry(sumFn)(7)(8)(6)); // 21
console.log(curry(sumFn, 7, 8)(6)); // 21
3. 柯里化函数的应用
let add1 = add4(1,2,3)
add1(4)//输出10
add1(5)//输出11
add1(6)//输出12
可以看到,我把add4
的参数1,2,3
给记忆下来了,然后如果其他地方得用到参数1,2,3
的话就没必要写了。
Function.prototype.bind
的实现Funtion.prototype.bind
的实现也利用了curry
化的原理,bind()
方法主要就是将函数绑定到某个对象,bind()
会创建一个函数,函数体内的this
对象的值会被绑定到传入bind()
中的第一个参数的值,例如:f.bind(obj)
,实际上可以理解为obj.f()
,这时f
函数体内的this
自然指向的是obj
;var a = {
b: function() {
var func = function() {
console.log(this.c);
}
func();
},
c: 'hello'
}
a.b(); // undefined 这里的this指向的是全局作用域
console.log(a.c); // hello
利用bind
方法:
var a = {
b: function() {
var func = function() {
console.log(this.c);
}.bind(this);
func();
},
c: 'hello'
}
a.b(); // hello
console.log(a.c); // hello
// 分析:这里的bind方法会把它的第一个实参绑定给f函数体内的this,所以里的this即指向{x:1}对象;
// 从第二个参数起,会依次传递给原始函数,这里的第二个参数2即是f函数的y参数;
// 最后调用m(3)的时候,这里的3便是最后一个参数z了,所以执行结果为1+2+3=6
// 分步处理参数的过程其实是一个典型的函数柯里化的过程(Curry)
function f(y,z){
return this.x+y+z;
}
var m = f.bind({x:1},2);
console.log(m(3)); // 6
实现bind
方法:
Function.prototype.my_bind = function() {
var self = this, // 保存需要绑定的this上下文
context = Array.prototype.shift.call(arguments),
// arguments 是类数组对象,它没有 shift 等数组独有的方法,想要弹出传入的参数中的第一个参数,就只有用这种方式了。
args = Array.prototype.slice.call(arguments); // 剩余的参数转为数组
return function() {
self.apply(context, Array.prototype.concat.call(args, Array.prototype.slice.call(arguments)));
}
};
//样例
function a(m, n, o) {
console.log(this.name + ' ' + m + ' ' + n + ' ' + o);
}
var b = {
name: 'kong'
};
a.my_bind(b, 7, 8)(9); // kong 7 8 9
不过往原型上加东西通常是不好的,所有还是写个bind
函数吧:
function bind(fn) {
var args = [].slice.call(arguments);
args.shift(); //除去fn
var that = args.shift();//this参数
return function () {
var args1 = [].slice.call(arguments);//第二部分参数
return fn.apply(that, args.concat(args1));
}
}
var getById = bind(document.getElementById, document);
getById("name");
4. 总结
在前面的几个例子中,我们可以总结一下柯里化的特点:
转载自
https://blog.csdn.net/daydream13580130043/article/details/83718978;
https://www.jianshu.com/p/5e1899fe7d6b
https://www.jianshu.com/p/7030376af23c