最近跟着黄轶老师
学习Vue.js 2.5.17-beta版本源码
时,看到了源码中用了函数柯里化
,这里来写一篇,记录一下,加深印象
柯里化是什么
百度百科上:
在计算机科学中,柯里化(Currying)
是把接受多个参数
的函数变换成接受一个单一参数
(最初函数的第一个参数)的函数,并且返回接受余下的参数
且返回结果的新函数
的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
简单例子实现柯里化
// 普通的add函数
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
console.log(add(1, 2)) // 3
console.log(curryingAdd(1)(2)) // 3
add函数
是接受多个参数
的函数,柯里化(Currying)后
,变成了curryingAdd函数,curryingAdd函数只接受一个单一参数
,并且返回接受余下参数
同时返回结果
的新函数
这个例子套上百度百科的解释,就可以比较清楚的知道什么是柯里化了
存在即合理,为什么人们要用柯里化呢?
柯里化好处
参数复用
// 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg, txt) {
return reg.test(txt);
}
check(/\d+/g, "hello world"); // false
check(/[a-z]+/g, "2020-05-11"); // false
// Currying后
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt);
};
}
// 字符串是否有数字
var hasNumber = curryingCheck(/\d+/g);
// 字符串是否有小写字母
var hasLetter = curryingCheck(/[a-z]+/g);
hasNumber("hello world"); // false
hasNumber("ES6"); // true
hasLetter("2020-05-11"); // false
上面是一个简单的正则校验,判断有没有包含数字,或者有没有包括小写字母;正常直接调用check函数就能满足;但是假设有很多地方都需要校验是否包括数字,小写字母的话,通过柯里化,就可以把check函数第一个参数复用起来
面试题
// 实现一个add方法,使计算结果能够满足如下预期:
+add(1)(2)(3) = 6;
+add(1, 2, 3)(4) = 10;
+add(1)(2)(3)(4)(5) = 15;
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
let _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包特性 保存_args并合并所有的参数值
let _adder = function() {
_args.push(...arguments);
return _adder;
};
// 利用js隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = _adder.valueOf = function() {
return _args.reduce((a, b) => a + b);
};
return _adder;
}
+add(1)(2)(3); // 6 _args从[1] 变为[1, 2] 再变为[1, 2, 3] 最后自动触发隐式转换toString()方法 得到数组各项的和6
+add(1, 2, 3)(4); // 10
+add(1)(2)(3)(4)(5); // 15
+add(2, 6)(1); // 9
通用的柯里化封装方法
function currying(fn, ...rest1) {
return function(...rest2) {
return fn.apply(null, rest1.concat(rest2))
}
}
用它将一个 sayHello 函数柯里化试试:
function sayHello(name, age, fruit) {
console.log(console.log(`我叫${name},我${age}岁了,我喜欢吃${fruit}`))
}
const curryingShowMsg1 = currying(sayHello)
curryingShowMsg1('小明', 22, '苹果') // 我叫小明,我22岁了,我喜欢吃 苹果
const curryingShowMsg2 = currying(sayHello, '小爱', 20)
curryingShowMsg2('西瓜')
// // 我叫小爱,我20岁了,我喜欢吃西瓜
加强版
function curryingHelper(fn, len) {
const length = len || fn.length // 第一遍运行length是函数fn一共需要的参数个数,以后是剩余所需要的参数个数
return function(...rest) {
return rest.length >= length // 检查是否传入了fn所需足够的参数
? fn.apply(this, rest)
: curryingHelper(currying.apply(this, [fn].concat(rest)), length - rest.length) // 在通用currying函数基础上
}
}
function sayHello(name, age, fruit) { console.log(`我叫 ${name},我 ${age} 岁了, 我喜欢吃 ${fruit}`) }
const betterShowMsg = curryingHelper(sayHello)
betterShowMsg('小阳', 20, '西瓜') // 我叫 小阳,我 20 岁了, 我喜欢吃 西瓜
betterShowMsg('小猪')(25, '南瓜') // 我叫 小猪,我 25 岁了, 我喜欢吃 南瓜
betterShowMsg('小明', 22)('倭瓜') // 我叫 小明,我 22 岁了, 我喜欢吃 倭瓜
betterShowMsg('小爱')(28)('冬瓜') // 我叫 小爱,我 28 岁了, 我喜欢吃 冬瓜
谢谢你阅读到了最后
期待你,点赞、评论、交流