31 函数柯里化相关
1 实现一个JS函数柯里化
预先处理的思想,利用闭包的机制
- 柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数
- 函数柯里化的主要作用和特点就是
参数复用、提前返回和延迟执行
- 柯里化把多次传入的参数合并,柯里化是一个高阶函数
- 每次都返回一个新函数
- 每次入参都是一个
当柯里化函数接收到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?
有两种思路:
- 通过函数的
length属性,获取函数的形参个数,形参的个数就是所需的参数个数 - 在调用柯里化工具函数时,手动指定所需的参数个数
将这两点结合一下,实现一个简单 curry 函数
通用版
// 写法1
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function(){
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this,fn,newArgs);
}else{
return fn.apply(this,newArgs);
}
}
}
// 写法2 推荐
function curry(fn) {
const fnArgsLength = fn.length // 传入函数的参数长度
let args = [] // 收集传入的参数
function calc(...newArgs) {
// 积累参数保存到闭包中
args = [
...args,
...newArgs
]
// 积累的参数长度跟传入函数的参数长度对比
if (args.length < fnArgsLength) {
// 参数不够,返回函数
return calc
} else {
// 参数够了,返回执行结果
return fn.apply(this, args.slice(0, fnArgsLength)) // 传入超过fnArgsLength长度的参数没有意义
}
}
// 返回一个函数
return calc
}
// 写法3
// 分批传入参数
// redux 源码的compose也是用了类似柯里化的操作
const curry = (fn, arr = []) => {// arr就是我们要收集每次调用时传入的参数
let len = fn.length; // 函数的长度,就是参数的个数
return function(...args) {
let newArgs = [...arr, ...args] // 收集每次传入的参数
// 如果传入的参数个数等于我们指定的函数参数个数,就执行指定的真正函数
if(newArgs.length === len) {
return fn(...newArgs)
} else {
// 递归收集参数
return curry(fn, newArgs)
}
}
}
// 测试
function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4)
ES6写法
const curry = (fn, arr = []) => (...args) => (
arg => arg.length === fn.length
? fn(...arg)
: curry(fn, arg)
)([...arr, ...args])
// 测试
let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10
// 柯里化求值
// 指定的函数
function sum(a,b,c,d,e) {
return a + b + c + d + e
}
// 传入指定的函数,执行一次
let newSum = curry(sum)
// 柯里化 每次入参都是一个参数
newSum(1)(2)(3)(4)(5)
// 偏函数
newSum(1)(2)(3,4,5)
// 柯里化简单应用
// 判断类型,参数多少个,就执行多少次收集
function isType(type, val) {
return Object.prototype.toString.call(val) === `[object ${type}]`
}
let newType = curry(isType)
// 相当于把函数参数一个个传了,把第一次先缓存起来
let isString = newType('String')
let isNumber = newType('Number')
isString('hello world')
isNumber(999)
2 请实现一个 add 函数,满足以下功能
add(1); // 1
add(1)(2); // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
function add(...args) {
// 在内部声明一个函数,利用闭包的特性保存并收集所有的参数值
let fn = function(...newArgs) {
return add.apply(null, args.concat(newArgs))
}
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
fn.toString = function() {
return args.reduce((total,curr)=> total + curr)
}
return fn
}
考点:
- 使用闭包, 同时要对JavaScript 的作用域链(原型链)有深入的理解
- 重写函数的
toSting()方法
// 测试,调用toString方法触发求值
add(1).toString(); // 1
add(1)(2).toString(); // 3
add(1)(2)(3).toString();// 6
add(1)(2, 3).toString(); // 6
add(1, 2)(3).toString(); // 6
add(1, 2, 3).toString(); // 6
3 实现 (5).add(3).minus(2) 功能
例: 5 + 3 - 2,结果为 6
Number.prototype.add = function(n) {
return this.valueOf() + n;
};
Number.prototype.minus = function(n) {
return this.valueOf() - n;
};
实现add(1)(2) =3
// 题意的答案
const add = (num1) => (num2)=> num2 + num1;
// 整了一个加强版 可以无限链式调用 add(1)(2)(3)(4)(5)....
function add(x) {
// 存储和
let sum = x;
// 函数调用会相加,然后每次都会返回这个函数本身
let tmp = function (y) {
sum = sum + y;
return tmp;
};
// 对象的toString必须是一个方法 在方法中返回了这个和
tmp.toString = () => sum
return tmp;
}
alert(add(1)(2)(3)(4)(5))
无限链式调用实现的关键在于 对象的 toString 方法 : 每个对象都有一个
toString()方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。
也就是我在调用很多次后,他们的结果会存在add函数中的sum变量上,当我alert的时候 add会自动调用 toString方法 打印出 sum, 也就是最终的结果
