如何使用函数式编程?
由于最近面试的原因,一直没有更新博文,最近有时间了,谈一下一直在研究的函数式编程的使用。
函数式编程,从接触以来似乎没怎么使用,据说backbone的依赖是underscore,是不是也是函数式呢?redux不依赖underscore,是不是函数式呢?
backbone这个不清楚,反正在redux中或多或少的使用了函数式编程的理念,用到函数式编程(以下简称为fp)的compose函数来处理它的reducer。
那么fp到底该怎么用,它有什么好处呢? 系统的学习fp可到这里来-->点我 当然,买本《functional javascript》也是不错的~
您也可以看一下我的这篇博文--《javascript柯里化及组合函数》里面讲了本例子所用到的主要函数~
-----------------------正文分割线-------------------------------------------
引用一句话说:我们要开始转变观念了,从现在开始,我们将不再指示计算机如何工作,而是指出我们明确希望得到的结果。
先举个栗子~
这是命令式编程
var makes = []; for (i = 0; i < cars.length; i++) { makes.push(cars[i].make); }
这是声明式编程
var makes = cars.map(function(car){ return car.make; });
我觉得高低立分,声明式的map函数是一个表达式,直观而且自由度很大。我们可以随意更改makes的内容,而这些都是命令式达不到的。fp的优势就是这样。
从书上找一个例子来说明fp是怎样编程的:
这个例子是获取图片,然后展示在浏览器上。
这是html结构:
<!DOCTYPE html> <html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.11/require.min.js"></script> <script src="flickr.js"></script> </head> <body></body> </html>
flickr.js的内容:
requirejs.config({ paths: { ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min', jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min' } }); require([ 'ramda', 'jquery' ], function (_, $) { var trace = _.curry(function(tag, x) { console.log(tag, x); return x; }); // app goes here });
ramda是一个fp的库,就类似于underscore和lodash,本例子用了requirejs,虽然我不怎么喜欢这个框架,为保持一致性,就一直用require了。
这个flickr.js就是用require加载了ramda和jquery,声明了trace函数用来在fp的流中打印当前流的值,其实排错用,暂时忽略它。
本应用的任务呢?
- 根据特定搜索关键字构造 url
- 向 flickr 发送 api 请求
- 把返回的 json 转为 html 图片
- 把图片放到屏幕上
上面提到了2个不纯的动作:从api获取数据和把图片放到屏幕上,我们先把不纯的动作写出来,以便分离开其他纯的动作。
var Impure = { getJSON: _.curry(function(callback, url) { $.getJSON(url, callback); }), setHtml: _.curry(function(sel, html) { $(sel).html(html); }) };
在 Impure
命名空间下,这样我们就知道它们都是危险函数。
简单了封装了jquery的getJSON和jquery的html。就可以用Impure.getJSON(url,callback)来调用啦。
var url = function (term) { return 'https://api.flickr.com/services/feeds/photos_public.gne?tags=' + term + '&format=json&jsoncallback=?'; };
封装获得url的函数。
好,我们把方法都写完了,现在开始fp之旅。
利用compose组合了获取url和获取图片这个任务。app("cats");就可以得到cats图片的src,回掉函数是trace("response"),它会打印服务器的返回。
var app = _.compose(Impure.getJSON(trace("response")), url); app("cats");
如果命令式编程的话会写为
var _url = url("cat"); Impure.getJSON(_url,trace("response"));
这样就不是那么简洁自由了。
这会调用 url
函数,然后把字符串传给 getJSON
函数。getJSON
已经局部应用了 trace
,加载这个应用将会把请求的响应显示在 console 里。
得到这个json后,我们想要从这个 json 里构造图片,但是 src 都在 items
数组中的每个 media
对象的 m
属性上
然后实现一个辅助函数:
var prop = _.curry(function(property, object){ return object[property]; });
这个其实就是_.prop了。很无聊是不是。。
然后呢,获取图片url,
var mediaUrl = _.compose(_.prop('m'), _.prop('media')); var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
从json里面取到items属性,然后取media属性,然后取m属性。。一系列的取值运算都用compose来执行。
然后把它整合到html里:
var renderImages = _.compose(Impure.setHtml("body"), srcs); var app = _.compose(Impure.getJSON(renderImages), url);
app方法是把字符串通过url形成需要捕获的url字符串,然后放到getJSON里获取,然后返回renderImage方法。
renderImage函数是把得到的src传到body里,body里会显示图片的src。
这个方法是真正把图片添加到html里。
var img = function (url) { return $('<img />', { src: url }); };
这是真正render图片的renderImages方法。与上面的renderImages方法不一样的地方就是在setHtml的时候不是把srcs放到body了,它加了img标签了。
images就是把srcs组装成html里的img标签。
var images = _.compose(_.map(img), srcs); var renderImages = _.compose(Impure.setHtml("body"), images); var app = _.compose(Impure.getJSON(renderImages), url);
这样就完成了~
最后执行
app("cats");
的时候,这一系列工具链启动了,cats传入url里面封装成需要捕获的url,然后传到getJSON里面获取。getJSON的callback又是renderImages,renderImages把得到的src组装到img标签,然后扔到body里。然后这些工作就完成了。
只说做什么,不说怎么做。这就是fp。我们只传入了cats,这是我们需要的cat图片,然后怎么做呢,这些函数通过compose完成自己的功能。然后通过compose组合起来,完成这个项目的应用。
是不是觉得清晰了很多,而且每一步都是可变的,就像gulp执行pipe,增加一个流的处理是很简单的,对管道的流的处理可以改变整个结果。
完整代码:
requirejs.config({ paths: { ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min', jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min' } }); require([ 'ramda', 'jquery' ], function (_, $) { //////////////////////////////////////////// // Utils var Impure = { getJSON: _.curry(function(callback, url) { $.getJSON(url, callback); }), setHtml: _.curry(function(sel, html) { $(sel).html(html); }) }; var img = function (url) { return $('<img />', { src: url }); }; var trace = _.curry(function(tag, x) { console.log(tag, x); return x; }); //////////////////////////////////////////// var url = function (t) { return 'https://api.flickr.com/services/feeds/photos_public.gne?tags=' + t + '&format=json&jsoncallback=?'; }; var mediaUrl = _.compose(_.prop('m'), _.prop('media')); var srcs = _.compose(_.map(mediaUrl), _.prop('items')); var images = _.compose(_.map(img), srcs); var renderImages = _.compose(Impure.setHtml("body"), images); var app = _.compose(Impure.getJSON(renderImages), url); app("cats"); });