frontend9 / fe9-interview

前端九部 - 面试题库
MIT License
414 stars 14 forks source link

有赞: sum(2, 3)实现sum(2)(3)的效果(一面) #21

Open IWSR opened 5 years ago

IWSR commented 5 years ago

To:

https://github.com/IWSR

面试公司:

有赞

面试环节:

网上找的

问题:

sum(2, 3)实现sum(2)(3)的效果

IWSR commented 5 years ago

思路

这是一个典型的函数柯里化的知识点,其定义为将接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数。

此题的要求为sum(2)(3)实现与sum(2, 3)的效果【暂且这么认为吧,知识点就是柯理化】 由题意可归纳为以下要求

此时可以大致归纳为要求实现一个方法,该方法需要返回另外一个方法,且返回的方法需要保持对原有的函数作用域内某变量的引用,很熟悉的要求,就是个闭包的结构。 以下放出初步的实现

function sum() {
  function add(a, b) {
    return a + b
  }
  // slice会返回一个新数组,这不会影响到arguments
  let args = Array.prototype.slice.call(arguments);
  // 返回一个函数可以提供二次调用
  return function() {
    // 此处的arguments为第二次调用时传入的参数
    let newArgs = args.concat(Array.prototype.slice.call(arguments))
    return add.apply(this, newArgs)
  }
}

但是这样的写法是局限的且不怎么好用,理由如下:

为了解决上面的问题,思路也很清晰,应该将add操作作为一个fn的形参交给使用者自己去定义,因此可得到下面的变种

// 改名为curry了
function curry(fn) {
  // slice会返回一个新数组,这不会影响到arguments
  let args = Array.prototype.slice.call(arguments, 1);
  // 返回一个函数可以提供二次调用
  return function() {
    // 此处的arguments为第二次调用时传入的参数
    let newArgs = args.concat(Array.prototype.slice.call(arguments))
    return fn.apply(this, newArgs)
  }
}

虽说fn成为了参数可以被自定义,但是缺陷也出现了,curry(add, a)(b)可满足sum(a, b) => sum(a)(b)的要求,但如果sum(a, b, c)那就只能凉凉,换句话说我们需要判断场景(fn的参数个数,以及当前已经传入的元素个数)来决定是返回一个可调用的函数还是fn的执行结果。

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

function curry(fn, length) {
    // 初始化时赋值为fn的形参个数,用以标示剩余需要传入参数的个数
    length = length || fn.length;

    const slice = Array.prototype.slice;

    return function() {
        if (arguments.length < length) {
            const combined = [fn].concat(slice.call(arguments));
            // length - arguments.length用以计数当前已传入的参数
            return curry(sub_curry.apply(this, combined), length - arguments.length);
        } else {
            return fn.apply(this, arguments);
        }
    };
}