前端必学——函数式编程

花门楼前见秋草,岂能贫贱相看老。这篇文章主要讲述前端必学——函数式编程相关的知识,希望能为你提供帮助。
本篇将着重介绍  函数的输入,它是 JS 轻量函数式编程的基础之基础,重要之重要!!!
?

前端必学——函数式编程

文章图片

偏函数传参现状我们经常会写出这样的代码:
function ajax(url,data,callback)
// ..


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



ajax  函数有三个入参,在  getPerson  函数里调用,其中 url 已确定,data 和 cb 两个参数则等待传入。(因为很多时候参数都不是在当前能确定的,需要等待其它函数的操作后确定了再继续传入)
但是我们的原则是:入参最理想的情况下只需一个!
怎样优化,可以实现这一点呢?
我们或许可以在外层再套一个函数来进一步确定传参,比如:
function getCurrentUser(cb)

...// 通过某些操作拿到 CURRENT_USER_ID

getPerson(user: CURRENT_USER_ID , cb );


这样,data 参数也已经确定,cb 参数仍等待传入;函数  getCurrentUser  就只有一个入参了!
数据的传递路线是:
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 轻量级函数式编程的面纱 ~
以上。
?
我是掘金安东尼: 一名人气前端技术博主(文章 100w+ 阅读量)
终身写作者(INFP 写作人格)
坚持与热爱(简书打卡 1000 日)
我能陪你一起度过漫长技术岁月吗(以梦为马)
觉得不错,给个点赞和关注吧(这是我最大的动力 )b( ̄▽ ̄)d

    推荐阅读