阅读 951

【你应该了解的】函数柯里化

团队:skFeTeam  本文作者:蔡旭光

一道面试题

已知一个add方法。

function add(a, b, c) {  
  return a + b + c;
}
add(1,2,3)   // 6
复制代码

实现一个sum方法,满足如下条件:

sum(1)(2,3)   // 6
sum(1)(2)(3)   // 6
复制代码

该题其实就运用了函数柯里化的思想,我们可以将add方法通过函数柯里化转化为sum方法。

先来看看什么是函数柯里化。

什么是函数柯里化

维基百科中这样定义柯里化:

计算机科学中,柯里化(英语:Currying),又译为卡瑞化加里化,是把接受多个参数函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

用大白话来说就是:只传递给函数一部分参数来调用它,让它返回一个新函数去处理剩下的参数。

举个例子理解柯里化:

假设你有一间商店,促销季,你想给普通顾客所有商品九折优惠。

function setDiscount(price, discount) {  
    return price * discount;
}
复制代码

当一个普通顾客买了一件价格为100元的商品,你计算价格可以:

const price = setDiscount(100, 0.9);
复制代码

你会发现,每次计算一个商品价格,都需要输入价格和折扣,很麻烦。

const price1 = setDiscount(500, 0.9);
const price2 = setDiscount(1000, 0.9);
const price3 = setDiscount(2000, 0.9);
复制代码

我们可以将函数setDiscount柯里化,避免每次都输入折扣。

function currySetDiscount(discount) {  
    return function(price) {    
        return discount * price;  
    };
}

const setNinetyPercent = currySetDiscount(0.9);
复制代码

现在我们可以这样计算普通顾客购买商品时的价格:

const price = setNinetyPercent(500);
复制代码

同样的,针对VIP顾客,所有商品打八折,可以这样计算:

const setEightyPercent = currySetDiscount(0.8);

const price = setEightyPercent(500);
复制代码

函数柯里化的这种作用,可以理解为参数复用,延迟执行,减少了代码冗余,增加了代码可读性

函数柯里化的简单实现

回到开始的那道题,我们来写一个curry函数。

function curry(fn, args) {  
    let len = fn.length;  // 待柯里化的函数的参数长度
    let tempArgs = args || [];  
    return function() {    
        tempArgs = tempArgs.concat([...arguments])    
        if (tempArgs.length < len) {      
            return curry.call(this, fn, tempArgs);    
        } else {      
            return fn.apply(this, tempArgs);    
        }  
    };
}
复制代码

原理:用闭包保存参数,当参数数量和原函数参数数量一致时,执行函数

可以这样来柯里化add,生成sum方法:

var sum = curry(add);

sum(1)(2,3)   // 6
sum(1)(2)(3)   // 6
复制代码

函数柯里化的应用

延迟计算

我们常用的bind函数。

let obj = {
  name: 'jack'
}
let showName = function() {
    console.log(this.name)
}
let showJackName = showName.bind(obj);

showJackName(); // jack
复制代码

这里bind用来改变函数执行时的上下文,但是函数本身并不执行,所以本质上是延迟计算。

我们看下bind的模拟实现,本质上就是一种柯里化。

function myBind(){
  var self = this;
  // 第一个对象参数
  var context = Array.prototype.shift.call(arguments);
  // 其余参数
  var bindArgs = Array.prototype.slice.call(arguments);
  // 临时函数
  var fTemp = function(){};
  function fn(){
    // 合并绑定参数以及调用时参数
    var args = bindArgs.concat(Array.prototype.slice.call(arguments));
    // 原函数执行(this指向给定对象)
    self.apply(context, args);
  }
  // 临时函数prototype指向原函数prototype
  fTemp.prototype = self.prototype;
  // 新函数prototype设为临时函数的实例对象(当原函数使用New创建实例)
  fn.prototype = new fTemp();
  return fn;
}
复制代码

总结

函数柯里化是函数式编程(一种编程范式)中的一个最基本的运算,它生于函数式编程,也主要服务于函数式编程(函数的组合前提是需要单参数的函数)。我们日常开发中其实无需特意区分是否使用函数柯里化,闭包,高阶函数等都一定程度上与函数柯里化有异曲同工之妙。

想了解skFeTeam更多的分享文章,可以点这里,谢谢~

关注下面的标签,发现更多相似文章
评论