函数式编程是一种将函数做为参数传递和返回,而且没有反作用的一种编程方式。JavaScript,Haskell,Clojure,Scala 和 Erlang 是部分实现了函数式编程的语言。函数式编程也带来了不少概念javascript
函数柯里化(currying)又称部分求值。一个 currying 的函数首先会接受一些参数,接受了这些参数以后,该函数并不会当即求值,而是继续返回另一个函数,刚才传入的参数在函数造成的闭包中被保存起来。待到函数被真正须要求值的时候,以前传入的全部参数都会被一次性用于求值。java
Currying is the process of turning a function with multiple arity into a function with less arity(柯里化是将一个多元函数转换为低元函数的操做)—Kristina Brainwavegit
举一个简单的例子github
function adds(a, b, c) {
return a + b + c
}
function currying(a) {
return function (b) {
return function(c){
return a + b + c
}
}
}
adds(2, 3, 4) // 9
currying(2)(3)(4) // 9
复制代码
上面currying函数能够实现较为简单的功能,结构也比较清晰,可是针对于更多参数的传递,咱们不能无限的嵌套下去,或者针对于不一样的传参方式,例如编程
currying(2, 3)(4)
currying(2, 3, 4)
currying(2)(3, 4)
复制代码
上面的函数就不能支持了,所以须要对currying函数进行下一步优化。浏览器
function adds(a, b, c) {
return a + b + c
}
function currying(fn, length){
length = length || fn.length; // 第一次调用获取函数 fn 参数的长度,后续调用获取 fn 剩余参数的长度
return function(){
var args = [].slice.call(arguments) // currying返回函数接受的参数
if (args.length < length) { // 判断参数的长度是否小于 fn 剩余参数须要接收的长度
return curry(fn.bind(this, ...args), length - args.length) // 递归 currying 函数,新的 fn 为 bind 返回的新函数(bind 绑定了 ...args 参数,未执行),新的 length 为 fn 剩余参数的长度
}else {
return fn.call(this, ...args) // 执行 fn 函数,传入新函数的参数
}
}
}
var addCurry = currying(adds);
addCurry(2)(3)(4) // 9
addCurry(2, 3)(4) // 9
addCurry(2, 3, 4) // 9
addCurry(2)(3, 4) // 9
复制代码
咱们经过判断是否传入足够数量的参数长度决定是执行函数仍是递归currying返回新的函数。其中用到了call和bind。若是不想用call\apply\bind的功能。还有大神提供了最简版本bash
const currying = fn =>
judge = (...args) =>
args.length >= fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg)
var addCurry = currying(adds);
addCurry(2)(3)(4) // 9
addCurry(2, 3)(4) // 9
addCurry(2, 3, 4) // 9
addCurry(2)(3, 4) // 9
复制代码
上述两个currying函数的实现原理都是「用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数」。闭包
function volume(l, w, h) {
return l * w * h
}
let volumeA = volume(100, 100, 50)
let volumeB = volume(100, 100, 90)
复制代码
在计算货物体积时咱们一般须要参入三个参数来计算体积,可是计算一样长和宽而不一样高的货物,长度和宽度没有必要每次都输入。这时候咱们的柯里化就能够运用于此,这样就减小重复传递不变的部分参数app
var getvolume = currying(volume)(100, 100)
volumeA(50)
volumeB(60)
复制代码
有一种典型的应用情景是这样的,每次调用函数都须要进行一次判断,但其实第一次判断计算以后,后续调用并不须要再次判断,这种状况下就很是适合使用柯里化方案来处理。即第一次判断以后,动态建立一个新函数用于处理后续传入的参数,并返回这个新函数。less
下面的例子中,在 DOM 中添加事件时须要兼容现代浏览器和 IE 浏览器(IE < 9),方法就是对浏览器环境进行判断,看浏览器是否支持。通常状况下每次添加事件都会调用作一次判断。
function addEvent (type, el, fn, capture = false) {
if (window.addEventListener) {
el.addEventListener(type, fn, capture);
}
else if(window.attachEvent){
el.attachEvent('on' + type, fn);
}
}
复制代码
可是咱们能够以利用闭包和当即调用函数表达式(IIFE)来使判断只用执行一次,动态建立新的函数用于处理后续传入的参数,这样作的好处就是以后调用就不须要再次计算了
const addEvent = (function(){
if (window.addEventListener) {
return function (type, el, fn, capture) {
el.addEventListener(type, fn, capture);
}
}
else if(window.attachEvent){
return function (type, el, fn) {
el.attachEvent('on' + type, fn);
}
}
})();
复制代码
bind 的模拟实现自己就是一种柯里化,bind是用来改变函数执行时候的上下文,可是函数自己并不执行,因此本质上是延迟计算
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
复制代码
经过一个函数实现下列功能
add(1) // 1
add(1)(2); // 3
add(1)(2)(3)// 6
add(1)(2)(3)(4) // 10
复制代码
初级版
function add(a) {
function sum(b) { // 使用闭包
a = a + b; // 累加
return sum;
}
sum.toString = function () { // 重写toString()方法
return a;
}
return sum; // 返回一个函数
}
复制代码
进阶版本
function add() {
let data = [].concat(Array.prototype.slice.call(arguments))
function tmp() { // 使用闭包
data = data.concat(Array.prototype.slice.call(arguments))
return tmp;
}
tmp.valueOf = function () {
return data.reduce(((source, item) => source + item), 0);
}
tmp.toString = function () {
return data.reduce(((source, item) => source + item), 0);
}
return tmp
}
add(1) // 1
add(1,4)(2); // 7
add(1)(2, 5)(3)// 11
add(1,5,6)(2,5)(3)(4, 4) // 30
复制代码