XDM,JS如何函数式编程?看这就够了!(二)

承接上一篇《XDM,JS如何函数式编程?看这就够了!(一)》,咱们知道了函数式编程的几个基本概念。ajax

这里做简要回顾编程

  1. 函数式编程目的是为了数据流更加明显,从而代码更具可读性;
  2. 函数须要一个或多个输入(理想状况下只需一个!)和一个输出,输入输出是显式的代码将更好阅读;
  3. 闭包是高阶函数的基础;
  4. 警戒匿名函数;
  5. 弃用 this 指向;

本篇将着重介绍第 2 点中函数的输入,它是 JS 轻量函数式编程的基础之基础,重要之重要!!!api

偏函数

传参现状

咱们常常会写出这样的代码:数组

function ajax(url,data,callback) {
    // ..
}

function getPerson(data,cb) {
    ajax( "http://some.api/person", data, cb );
}

复制代码

ajax 函数有三个入参,在 getPerson 函数里调用,其中 url 已肯定,data 和 cb 两个参数则等待传入。(由于不少时候参数都不是在当前能肯定的,须要等待其它函数的操做后肯定了再继续传入)markdown

可是咱们的原则是:入参最理想的状况下只需一个!闭包

怎样优化,能够实现这一点呢?函数式编程

咱们或许能够在外层再套一个函数来进一步肯定传参,好比:函数

function getCurrentUser(cb) {
    
    ...// 经过某些操做拿到 CURRENT_USER_ID

    getPerson( { user: CURRENT_USER_ID }, cb );
}
复制代码

这样,data 参数也已经肯定,cb 参数仍等待传入;函数 getCurrentUser 就只有一个入参了!post

数据的传递路线是:优化

ajax(url,data,callback) => getPerson(data,cb) => getCurrentUser(cb)
复制代码

这样函数参数个数逐渐减小的过程就是偏应用

也能够说:getCurrentUser(cb)getOrder(data,cb) 的偏函数,getOrder(data,cb)ajax(url,data,cb) 函数的偏函数

设想下:

若是一个函数是这样的:

function receiveMultiParam(a,b,c,......,x,y,z){
    // ..
}
复制代码

咱们难道还要像上面那样手动指定外层函数进行逐层嵌套吗?

显示咱们不会这么作!

封装 partial

咱们只须要封装一个 partial(..) 函数:

function partial(fn,...presetArgs) {
    return function partiallyApplied(...laterArgs){
        return fn( ...presetArgs, ...laterArgs );
    };
}
复制代码

它的基础逻辑是:

var partial =
    (fn, ...presetArgs) =>
        (...laterArgs) =>
            fn( ...presetArgs, ...laterArgs );
复制代码

把函数做为入参!还记得咱们以前所说:

一个函数若是能够接受或返回一个甚至多个函数,它被叫作高阶函数。

咱们借用 partial() 来实现上述举例:

var getPerson = partial( ajax, "http://some.api/person" );

var getCurrentUser = partial( getPerson, { user: CURRENT_USER_ID } ); // 版本 1
复制代码

如下函数内部分析很是重要:

运行机制

getPerson() 的内部运行机制是:

var getPerson = function partiallyApplied(...laterArgs) {
    return ajax( "http://some.api/person", ...laterArgs );
};
复制代码

getCurrentUser() 的内部运行机制是:

var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs) {
    var getPerson = function innerPartiallyApplied(...innerLaterArgs){
        return ajax( "http://some.api/person", ...innerLaterArgs );
    };

    return getPerson( { user: CURRENT_USER_ID }, ...outerLaterArgs );
}
复制代码

数据进行了传递:

getCurrentUser(outerLaterArgs) => getPerson(innerLaterArgs) => ajax(...params)
复制代码

咱们经过这样一层额外的函数包装层,实现了更增强大的数据传递,

咱们将须要减小参数输入的函数传入 partial()中做为第一个参数,剩下的是 presetArgs,当前已知几个,就能够写几个。还有不肯定的入参 laterArgs,能够在肯定后继续追加。

像这样进行额外的高阶函数包装层,是函数式编程的精髓所在!

“随着本系列的继续深刻,咱们将会把许多函数互相包装起来。记住,这就是函数式编程!” —— 《JavaScript 轻量级函数式编程》

实际上,实现 getCurrentUser() 还能够这样写:

// 版本 2
var getCurrentUser = partial(
    ajax,
    "http://some.api/person",
    { user: CURRENT_USER_ID }
);

// 内部实现机制

var getCurrentUser = function partiallyApplied(...laterArgs) {
    return ajax(
        "http://some.api/person",
        { user: CURRENT_USER_ID },
        ...laterArgs
    );
};
复制代码

可是版本 1 由于重用了已经定义好的函数,因此它在表达上更清晰一些。它被认为更加贴合函数式编程精神!

拓展 partial

咱们再看看 partial() 函数还可它用:

function partial(fn,...presetArgs) {
    return function partiallyApplied(...laterArgs){
        return fn( ...presetArgs, ...laterArgs );
    };
}
复制代码

好比:将数组 [1,2,3,4,5] 每项都加 3,一般咱们会这么作:

function add(x,y) {
    return x + y

[1,2,3,4,5].map( function adder(val){
    return add( 3, val );
} );

// [4,5,6,7,8]
复制代码

借助 partial()

[1,2,3,4,5].map( partial( add, 3 ) );

// [4,5,6,7,8]
复制代码

add(..) 不能直接传入 map(..) 函数里,经过偏应用进行处理后则能传入;

实际上,partial() 函数还能够有不少变体:

回想咱们以前调用 Ajax 函数的方式:ajax( url, data, cb )。若是要偏应用 cb 而稍后再指定 data 和 url 参数,咱们应该怎么作呢?

function reverseArgs(fn) {
    return function argsReversed(...args){
        return fn( ...args.reverse() );
    };
}

function partialRight( fn, ...presetArgs ) {
    return reverseArgs(
        partial( reverseArgs( fn ), ...presetArgs.reverse() )
    );
}

var cacheResult = partialRight( ajax, function onResult(obj){
    cache[obj.id] = obj;
});

// 处理后:
cacheResult( "http://some.api/person", { user: CURRENT_USER_ID } );
复制代码

柯里化

函数柯里化其实是一种特殊的偏函数。

咱们用 curry(..) 函数来实现此前的 ajax(..) 例子,它会是这样的:

var curriedAjax = curry( ajax );

var personFetcher = curriedAjax( "http://some.api/person" );

var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } );

getCurrentUser( function foundUser(user){ /* .. */ } );
复制代码

柯里化函数:接收单一实参(实参个数:1)并返回另外一个接收下一个实参的函数。

它将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。

实现:

function curry(fn,arity = fn.length) {
    return (function nextCurried(prevArgs){
        return function curried(nextArg){
            var args = prevArgs.concat( [nextArg] );

            if (args.length >= arity) {
                return fn( ...args );
            }
            else {
                return nextCurried( args );
            }
        };
    })( [] );
}
复制代码

阶段小结

咱们为何要如此着重去谈“偏函数”(partial(sum,1,2)(3))或“柯里化”(sum(1)(2)(3))呢?

第一,是显而易见的,偏函数或柯里化,能够将“指定分离实参”的时机和地方独立开来;

第二,更有重要意义的是,当函数只有一个形参时,咱们可以比较容易地组合它们。这种单元函数,便于进行后续的组合函数;

对函数进行包装,使其成为一个高阶函数是函数式编程的精髓!

至此,有了“偏函数”这门武器大炮,咱们将逐渐轰开 JS 轻量级函数式编程的面纱 ~

以上。

我是掘金安东尼,公众号【掘金安东尼】,点赞👍关注👀,输出暴露输入!持续追踪!

相关文章
相关标签/搜索