一 函数柯里化的概念
维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术
普通函数与柯里化函数调用对比
普通函数
function add(a, b) { return a + b }
add(1, 2) // 3
柯里化
function curry(a) {
return function(b) {
return a + b
}
}
curry(1)(2) // 3
这种的话就只可以传入两个参数,假如多个参数的话,就要优化,例如curry(1)(2).....(n)
来列一列Currying有哪些好处呢?
参数复用
// 正常正则验证字符串 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
提前确认
var on = function(isSupport, element, event, handler) {
isSupport = isSupport || document.addEventListener;
if (isSupport) {
return element.addEventListener(event, handler, false);
} else {
return element.attachEvent('on' + event, handler);
}
}
延迟运行
Function.prototype.bind = function (context) {
var _this = this
var args = Array.prototype.slice.call(arguments, 1)
return function() {
return _this.apply(context, args)
}
}
封装
function curryAdd (fn) {
// args 获取第一个方法内的全部参数
var args = Array.prototype.slice.call(arguments, 1)
return function() {
// 将后面方法里的全部参数和args进行合并
var newArgs = args.concat(Array.prototype.slice.call(arguments))
// 把合并后的参数通过apply作为fn的参数并执行
return fn.apply(this, newArgs)
}
}
function add(a, b, c) {
return a + b + c
}
// 调用方式一
const curry = curryAdd(add, 5)
console.log(curry(1,2))
// 调用方式二
const curry = curryAdd(add)
console.log(curry(1, 2, 3))
这里参数,传入参数多的话,只计算前面三个参数,所以还是存在局限性
进一步封装
// 支持多参数传递
function progressCurrying(fn, args) {
var _this = this
var len = fn.length;
var args = args || [];
return function() {
var _args = Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args, _args);
// 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
if (_args.length < len) {
return progressCurrying.call(_this, fn, _args);
}
// 参数收集完毕,则执行fn
return fn.apply(this, _args);
}
}
function add(a, b, c) {
return a + b + c
}
const curry = progressCurrying(add)
console.log(curry(1)(2)(3))
这里的curry调用根据add的参数来决定,add函数多一个,curry可以多调用一次
一道经典面试题
// 实现一个add方法,使计算结果能够满足如下预期:
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
const _slice = Array.prototype.slice
const _args = _slice.call(arguments)
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
const _add = function() {
const args = _slice.call(arguments)
_args.push(...args)
return _add
}
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_add.toString = function() {
return _args.reduce((a, b) => a + b, 0)
}
return _add
}
add(1)(2)(3) = f 6;
add(1, 2, 3)(4) = f 10;
add(1)(2)(3)(4)(5) = f 15;
console.log(add(1)(2).toString()) // 3
或者换一个方法
function curry(fn, scope, args) {
let _args = arg // 保存上次参数
let context = scope // 保存作用域
let add = (...rest) => {
let last = [...args, ...rest] // 合并参数
return (
// 当没有入参时候, 执行函数,否则继续柯里化
rest.length > 0 ? curry(fn,context, last) : fn.call(context, last)
)
}
return add
}
function sum(...arguments) {
return arguments.reduce((a, b) => a + b, 0)
}
const curryAdd = curry(sum)
console.log(currAdd(1)(2)(3)())
参考链接:
https://www.jianshu.com/p/2975c25e4d71