函数柯里化

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数柯里化相关的知识,希望对你有一定的参考价值。

所谓"柯里化",就是把一个多参数的函数,转化为单参数函数,所有函数只接受一个参数。它是一种"预加载"函数的方法,通过传递部分参数来调用函数,然后让函数返回另一个函数去处理剩下的参数。某种意义上讲,这是一种对参数的"缓存",是一种非常高效的编写函数的方法。

// 柯里化之前
function add(x, y) {
  return x + y;
}
add(1, 2); // 3

// 柯里化之后
function addX(y) {
  return function (x) {
    return x + y;
  };
}
addX(2)(1); // 3

一、柯里化的作用
1、参数复用;
2、提前返回;
3、延迟计算/运行。

二、举例说明

1、实现curry(fn)(1)(2)(3)

var fn = function(a,b,c){
  return a+b+c;
}

需要写一个函数,满足curry(fn)(1)(2)(3) //6

var fn = function(a,b,c) {
  return a+b+c;
}

function curry(fn) {
  var arr = [],
  mySlice = arr.slice;
  fnLen = fn.length;

  function curring() {
    arr = arr.concat(mySlice.call(arguments));
    if(arr.length < fnLen) {
      return curring;
    }
    return fn.apply(this, arr);
  }
  return curring;
}
curry(fn)(
1)(2)(3);//6

2、实现sum(1, 2, 3)(10) //16

//方案A
var sum = function() {
  var cache;
  if (arguments.length === 1) {
    cache = arguments[0];
    return function ( number ) {return cache + number;};
  } else return Array.prototype.reduce.call(arguments, function(a,b){
    return a+b;
  });
};

该方法有缺陷,只能计算sum(1, 2, 3, 4),如果计算sum(1, 2)(3, 4),会报错。sum的时候,如何既返回一个值,又返回一个函数以供后续调用?这里牵扯到函数的转换,不难想到valueOf()或是toString()。
下面插播一段儿,定义一个函数,

function test(){
  console.log(15);
}

如果仅仅调用test而不是test(),会发现test函数被打印了一遍,并没有执行。其实,这里自行调用了函数的valueOf()方法,也就是说,test和test.valueOf()结果相同。valueOf()返回对象的原始值,toString()返回对象的string值,如果二者都不存在,就抛出错误。

//方案B
var sum = (function() {
  var argArr = [];

  var sumFn = function() {
    // 拼接数组
    var args = Array.prototype.slice.call(arguments);
    argArr = argArr.concat(args);
    return sumFn;
  }
  // 覆盖 toString 方法
  sumFn.toString = function() {
    // 计算总和
    var sum = argArr.reduce(function(pre, next) {
      return pre + next;
    });
    // 清除记录
    argArr.length = 0;
    return sum;
  }

  return sumFn;
})();

如果只改写valueOf()或是toString()其中一个,会优先调用被改写了的方法,而如果两个同时改写,则会像Number类型转换规则一样,优先查询valueOf()方法,在valueOf()方法返回的是非原始类型的情况下,再查询toString()方法。

这里,只有最后一次调用才真正调用到toString(),而之前的操作都是合并参数,递归调用本身,由于最后一次调用返回的是一个sumFn函数,所以最终调用了函数的sumFn.toString(),并且利用了 reduce方法对所有参数求和。

//方案C
function sum() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var argArr = [].slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存argArr并收集所有的参数值
    var sumResult = function () {
        var sumFn = function() {
            [].push.apply(argArr, [].slice.call(arguments));
            return sumFn;
        };

        // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
        sumFn.toString = function () {
            return argArr.reduce(function (a, b) {
                return a + b;
            });
        }

        return sumFn;
    }
    return sumResult.apply(null, argArr);
}
//方案D
function sum(...args) {
  return args.reduce((a, b) => a + b);
}
//方案E
function sum() {
  // 第一次执行时,定义一个数组专门用来存储所有的参数
  var argArr = [].slice.call(arguments);

  // 在内部声明一个函数,利用闭包的特性保存argArr并收集所有的参数值
  var sumResult = function () {
    var sumFn = function() {
      argArr.push(...arguments);
      return sumFn;
    };

    // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    sumFn.toString = function () {
      return argArr.reduce(function (a, b) {
        return a + b;
      });
    }

    return sumFn;
  }
  return sumResult(...argArr);
}
/**
 * 测试用例
 */

sum(1)(2) // 3
sum(1, 2, 3)(10) // 16
sum(1)(2)(3)(4)(5) // 15

var a = sum(1)(2)(3)(4);   // 10
var b = sum(1, 2, 3, 4);   // 10
var c = sum(1, 2)(3, 4);   // 10
var d = sum(1, 2, 3)(4);   // 10

// 可以利用隐式转换的特性参与计算
a + 10; // 20
b + 20; // 30
c + 30; // 40
d + 40; // 50

// 也可以继续传入参数,得到的结果再次利用隐式转换参与计算
a(10) + 100;  // 120
b(10) + 100;  // 120
c(10) + 100;  // 120
d(10) + 100;  // 120

 

以上是关于函数柯里化的主要内容,如果未能解决你的问题,请参考以下文章

手写柯里化,实现柯里化

js高阶函数应用—函数柯里化和反柯里化

柯里化函数快速排序外边距重叠

函数式编程:纯函数&柯里化&组合函数

函数柯里化

JS中的柯里化及精巧的自动柯里化实现