【面试】函数柯里化

80 阅读3分钟

题目一 手写函数柯里化

首先搞清楚什么是函数柯里化,是一种将n个参数的一个函数转换为n个函数一个参数的技术。

作用:参数复用,延迟执行

  1. 获取传入的函数的参数个数
  2. 返回函数中判断,
    • 如果当前收集的函数参数没有到达最大值,则递归curry函数,将参数收集起来传入函数
    • 否则则调用该函数传入的函数fn
// 使用于参数数量固定函数
var curry = function(fn, args) {
    const len = fn.length // 代表传入函数的形参个数
    args = args || []
    return function() {
        // 重新定义一个参数集合
        let innerArgs = args.concat([...arguments]);
        if (args.length >= len) {
            return fn.apply(this, innerArgs);
        } else { // 到达参数个数前收集函数
            return curry.call(this, fn, innerArgs);
        }
    }
}

题目二 实现add(1)(2)(3)

写了上面的函数curry之后,这个就直接可以用了

function add(a, b, c) {
    return a + b + c
}

fn = curry(add);
fn(2)(3)(4)
fn(2, 3)(4)

如果硬写,就是下面的,

function add(a) {
    return function (b) {
        return function (c) {
            return a + b + c;
        }
    }
}

如果add函数改为不定参,则上面通过fn.length获取形参个数的方式就不生效,会返回0,因为fn.length是不包括rest参数的。所以需要标志来标识结束,假如用空参数代表调用结束,则可以一直搜集参数,直到想返回了,传空参数即可。

var curry2 = function(fn, args) {
    args = args || []
    return function() {
        let innerArgs = args.concat([...arguments]);
        if (!arguments.length) {
            return fn.apply(this, innerArgs);
        } else { // 到达参数个数前收集函数
            return curry2.call(this, fn, innerArgs);
        }
    }
}

// 测试用例
function add(...args) {return args.reduce((a, b) => a + b)}

fn = curry2(add);
console.log(fn(2)(3)(4)())
console.log(fn(2,3)(4)(7)(9)())
console.log(fn(2,3)(4)(5)(6)())

题目三 偏函数实现

也是返回一个函数,偏函数是固定一些参数,产生更小元的函数,就是分两次传递参数,和bind的传参数的方式类似。

function partial(fn) {
    const args = [].slice.call(arguments, 1);
    return function () {
        const innerArgs = args.concat([].slice.call(arguments));
        return fn.apply(this, innerArgs);
    }
}

// 测试用例
function sum(a, b, c) {
    return a + b + c
}
var fn = partial(sum, 1);
console.log(fn(2, 3))

function sum2(...args) {
    return args.reduce((a, b) => a + b)
}
fn2 = partial(sum2, 1, 2);
console.log(fn2(3, 4, 5))

偏函数和柯里化区别:

  1. 柯里化是将n个参数的函数转换为n个一个参数的函数
  2. 偏函数是将n个参数的函数转换成一个n-x参数的函数,x函数预置,参数进行复用。

题目四 函数组合实现

根据上面几个题,就想到了函数式编程的组合函数。

是将多个简单的函数组合成一个复杂的函数,每个函数的执行结果作为下一个函数的参数传递过去,最后一个函数的执行完就是结果。类似于linux中管道的概念,但是顺序跟它不一样。

  • compose:函数从右向左执行
  • pipe:函数从左向右执行

目的:

  • 避免洋葱嵌套代码,提高可读性
  • 组合单一功能生成多功能的新函数
  • 可以把复杂函数拆成多个简单函数,然后组合后使用。
// 初级版
var compose = function(f, g) {
    return function(x) {
        return f(g(x));
    }
}

// 遍历版本
var compose = function(...args) {
    let start = args.length - 1;
    return function () {
        let i = start;
        while (i >= 0) {
            let result = args[i].apply(this, arguments)
            return result;
        }
    }
}

// 传入一系列函数,从右向左执行,前一个函数的结果是下一个函数的参数,返回最后的结果
var compose = function(...fns) {
    return function (...args) {
        const first = fns.pop();
        const firstResult = first.apply(this, args);
        return fns.reduceRight((result, next) => next.call(this, result), firstResult)
    };
}

function compose(...fns) {
    return (value) => {
        return fns.reduceRight((value, fn) =>{
            return fn(value);
        }, value);
    }
}

// 测试用例:对输入内容进行格式处理,如,金额格式化:去除首尾空格、长度限制、千分位格式化。
var trim = (str) => String.prototype.trim.call(str)
var limit = (str) => String.prototype.substr.call(str,0,11)
var numberic = (str) => String(str).replace(/(\d{1,3})(?=(\d{3})+(?:$|\.))/g, "$1,")

var format = compose(numberic,limit,trim)

console.log(format(10000)) // 10,000
console.log(format(123000000)) // 123,000,000
console.log(format(' 123456789000000 ')) // 12,345,678,900

参考

函数式编程--函数组合(Function composition) JavaScript函数式编程之compose和pipe的理解和实现