Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

柯里化 #29

Open
hsipeng opened this issue Feb 26, 2018 · 0 comments
Open

柯里化 #29

hsipeng opened this issue Feb 26, 2018 · 0 comments

Comments

@hsipeng
Copy link
Owner

hsipeng commented Feb 26, 2018

在前端面试中,你可能会遇到这样一个涉及到柯里化的题目。

// 实现一个add方法,使计算结果能够满足如下预期:

add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

这个题目的目的是想让add执行之后返回一个函数能够继续执行,最终运算的结果是所有出现过的参数之和。而这个题目的难点则在于参数的不固定。我们不知道函数会执行几次。因此我们不能使用上面我们封装的createCurry的通用公式来转换一个柯里化函数。只能自己封装,那么怎么办呢?在此之前,补充2个非常重要的知识点。

一个是ES6函数的不定参数。假如我们有一个数组,希望把这个数组中所有的子项展开传递给一个函数作为参数。那么我们应该怎么做?

// 大家可以思考一下,如果将args数组的子项展开作为add的参数传入
function add(a, b, c, d) {
    return a + b + c + d;
}
var args = [1, 3, 100, 1];

在ES5中,我们可以借助之前学过的apply来达到我们的目的。

add.apply(null, args); // 105
而在ES6中,提供了一种新的语法来解决这个问题,那就是不定参。写法如下:
```javascript

add(...args); // 105

这两种写法是等效的。OK,先记在这里。在接下的实现中,我们会用到不定参数的特性。

第二个要补充的知识点是函数的隐式转换。当我们直接将函数参与其他的计算时,函数会默认调用toString方法,直接将函数体转换为字符串参与计算。

function fn() { return 20 }
console.log(fn + 10);     // 输出结果 function fn() { return 20 }10

但是我们可以重写函数的toString方法,让函数参与计算时,输出我们想要的结果。

function fn() { return 20; }
fn.toString = function() { return 30 }

console.log(fn + 10); // 40

除此之外,当我们重写函数的valueOf方法也能够改变函数的隐式转换结果。

function fn() { return 20; }
fn.valueOf = function() { return 60 }

console.log(fn + 10); // 70

当我们同时重写函数的toString方法与valueOf方法时,最终的结果会取valueOf方法的返回结果。

function fn() { return 20; }
fn.valueOf = function() { return 50 }
fn.toString = function() { return 30 }

console.log(fn + 10); // 60

补充了这两个知识点之后,我们可以来尝试完成之前的题目了。add方法的实现仍然会是一个参数的收集过程。当add函数执行到最后时,仍然返回的是一个函数,但是我们可以通过定义toString/valueOf的方式,让这个函数可以直接参与计算,并且转换的结果是我们想要的。而且它本身也仍然可以继续执行接收新的参数。实现方式如下。

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

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

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

        return _adder;
    }
    // return adder.apply(null, _args);
    return adder(..._args);
}

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

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

// 也可以继续传入参数,得到的结果再次利用隐式转换参与计算
console.log(a(10) + 100);  // 120
console.log(b(10) + 100);  // 120
console.log(c(10) + 100);  // 120
console.log(d(10) + 100);  // 120
// 其实上栗中的add方法,就是下面这个函数的柯里化函数,只不过我们并没有使用通用式来转化,而是自己封装
function add(...args) {
    return args.reduce((a, b) => a + b);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant