一文了解JS的函数柯里化

最近跟着黄轶老师学习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 岁了, 我喜欢吃 冬瓜

谢谢你阅读到了最后
期待你,点赞、评论、交流

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值