31 函数柯里化相关

1 实现一个JS函数柯里化

预先处理的思想,利用闭包的机制

  • 柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数
  • 函数柯里化的主要作用和特点就是参数复用提前返回延迟执行
  • 柯里化把多次传入的参数合并,柯里化是一个高阶函数
  • 每次都返回一个新函数
  • 每次入参都是一个

当柯里化函数接收到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?

有两种思路:

  • 通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数
  • 在调用柯里化工具函数时,手动指定所需的参数个数

将这两点结合一下,实现一个简单 curry 函数

通用版

    // 写法1
    function curry(fn, args) {
      var length = fn.length;
      var args = args || [];
      return function(){
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        if (newArgs.length < length) {
            return curry.call(this,fn,newArgs);
        }else{
            return fn.apply(this,newArgs);
        }
      }
    }
    // 写法2 推荐
    function curry(fn) {
        const fnArgsLength = fn.length // 传入函数的参数长度
        let args = [] // 收集传入的参数
    
        function calc(...newArgs) {
            // 积累参数保存到闭包中
            args = [
                ...args,
                ...newArgs
            ]
            // 积累的参数长度跟传入函数的参数长度对比
            if (args.length < fnArgsLength) {
                // 参数不够,返回函数
                return calc
            } else {
                // 参数够了,返回执行结果
                return fn.apply(this, args.slice(0, fnArgsLength)) // 传入超过fnArgsLength长度的参数没有意义
            }
        }
    
        // 返回一个函数
        return calc
    }
    // 写法3
    // 分批传入参数
    // redux 源码的compose也是用了类似柯里化的操作
    const curry = (fn, arr = []) => {// arr就是我们要收集每次调用时传入的参数
      let len = fn.length; // 函数的长度,就是参数的个数
    
      return function(...args) {
        let newArgs = [...arr, ...args] // 收集每次传入的参数
    
        // 如果传入的参数个数等于我们指定的函数参数个数,就执行指定的真正函数
        if(newArgs.length === len) {
          return fn(...newArgs)
        } else {
          // 递归收集参数
          return curry(fn, newArgs)
        }
      }
    }
    // 测试
    function multiFn(a, b, c) {
      return a * b * c;
    }
    
    var multi = curry(multiFn);
    
    multi(2)(3)(4);
    multi(2,3,4);
    multi(2)(3,4);
    multi(2,3)(4)

ES6写法

    const curry = (fn, arr = []) => (...args) => (
      arg => arg.length === fn.length
        ? fn(...arg)
        : curry(fn, arg)
    )([...arr, ...args])
    // 测试
    let curryTest=curry((a,b,c,d)=>a+b+c+d)
    curryTest(1,2,3)(4) //返回10
    curryTest(1,2)(4)(3) //返回10
    curryTest(1,2)(3,4) //返回10
    // 柯里化求值
    // 指定的函数
    function sum(a,b,c,d,e) {
      return a + b + c + d + e
    }
    
    // 传入指定的函数,执行一次
    let newSum = curry(sum)
    
    // 柯里化 每次入参都是一个参数
    newSum(1)(2)(3)(4)(5)
    
    // 偏函数
    newSum(1)(2)(3,4,5)
    // 柯里化简单应用
    // 判断类型,参数多少个,就执行多少次收集
    function isType(type, val) {
      return Object.prototype.toString.call(val) === `[object ${type}]`
    }
    
    let newType = curry(isType)
    
    // 相当于把函数参数一个个传了,把第一次先缓存起来
    let isString = newType('String')
    let isNumber = newType('Number')
    
    isString('hello world')
    isNumber(999)

2 请实现一个 add 函数,满足以下功能

    add(1); 			// 1
    add(1)(2);  	// 3
    add(1)(2)(3)// 6
    add(1)(2, 3); // 6
    add(1, 2)(3); // 6
    add(1, 2, 3); // 6
    function add(...args) {
      // 在内部声明一个函数,利用闭包的特性保存并收集所有的参数值
      let fn = function(...newArgs) {
       return add.apply(null, args.concat(newArgs))
      }
      
      // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
      fn.toString = function() {
        return args.reduce((total,curr)=> total + curr)
      }
      
      return fn
    }

考点:

  • 使用闭包, 同时要对JavaScript 的作用域链(原型链)有深入的理解
  • 重写函数的 toSting()方法
    // 测试,调用toString方法触发求值
    
    add(1).toString(); 			// 1
    add(1)(2).toString();  	// 3
    add(1)(2)(3).toString()// 6
    add(1)(2, 3).toString(); // 6
    add(1, 2)(3).toString(); // 6
    add(1, 2, 3).toString(); // 6

3 实现 (5).add(3).minus(2) 功能

例: 5 + 3 - 2,结果为 6

    Number.prototype.add = function(n) {
      return this.valueOf() + n;
    };
    Number.prototype.minus = function(n) {
      return this.valueOf() - n;
    };

实现add(1)(2) =3

    // 题意的答案
    const add = (num1) => (num2)=> num2 + num1;
    
    // 整了一个加强版 可以无限链式调用 add(1)(2)(3)(4)(5)....
    function add(x) {
      // 存储和
      let sum = x;
        
      // 函数调用会相加,然后每次都会返回这个函数本身
      let tmp = function (y) {
        sum = sum + y;
        return tmp;
      };
      
      // 对象的toString必须是一个方法 在方法中返回了这个和
      tmp.toString = () => sum
      return tmp;
    }
    
    alert(add(1)(2)(3)(4)(5))

无限链式调用实现的关键在于 对象的 toString 方法 : 每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。

也就是我在调用很多次后,他们的结果会存在add函数中的sum变量上,当我alert的时候 add会自动调用 toString方法 打印出 sum, 也就是最终的结果

Last Updated:
Contributors: leeguooooo