When I was in Beijing in early April, classmate Xu Hao said that the articles written by colleagues in our company were too simple and paid too much attention to details. Then I picked up the sesame seeds and lost the watermelon, so I didn’t Update the blog again (actually the root cause is that the project is too busy). Last week, I participated in the "Martin Fowler Shenzhen Tour" event with several other colleagues. My colleague Tashi and I contributed a "FullStack Language JavaScript", together with Yang Yun (known as the Big Devil in Jianghu) )'s topic is "Mastering Functional Programming and Controlling System Complexity", and Li Xin's (known as Xin Ye in Jianghu)'s topic is "Concurrency: Past and Afterlife".
When I was rehearsing with other colleagues, I suddenly discovered that our topics were more or less related. The part I talked about also involved event-based concurrency mechanisms and functional programming. If you think about it carefully, it should be related to the characteristics of JavaScript itself:
Event-Based Node.js is very typical in concurrency A model
Functional programming makes it naturally support callbacks, making it very suitable for asynchronous/event mechanisms
Functional programming features make it very Suitable for writing DSL
The day after the meeting, I suddenly wanted to rewrite an aggregation model using functional programming in the project code , and found that the idea was vaguely related to NoSQL, and further discovered that I had many shortcomings.
The following example comes from a scene in an actual project, but the Domain is switched, but it does not affect the reading and understanding of the mechanism behind it at all.
Imagine an application where users can see a list of subscribed RSS. Each item in the list (called a feed) contains an id
, an article title title
and a link to the article url
.
The data model looks like this:
var feeds = [ { 'id': 1, 'url': 'http://abruzzi.github.com/2015/03/list-comprehension-in-python/', 'title': 'Python中的 list comprehension 以及 generator' }, { 'id': 2, 'url': 'http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/', 'title': '使用inotify/fswatch构建自动监控脚本' }, { 'id': 3, 'url': 'http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/', 'title': '使用underscore.js构建前端应用' } ];
The model is very simple when this simple application does not have any user-related information. But soon, the application needed to be expanded from the stand-alone version to the Web version, that is to say, we introduced the concept of users. Every user can see one such list. In addition, users can also collect feeds. Of course, after collecting, users can also view the list of collected feeds.
Since each user can collect multiple feeds, and each feed can also be collected by multiple users, the many-to-many relationship between them is as shown in the figure above. . You may also think of things like:
$ curl http://www.php.cn/:9999/user/1/feeds
to get all feed
of user 1
, etc., but these are not important. The real problem is, when you get After all the feeds, on the UI, you need to add a attribute makred
for each feed. This attribute is used to indicate whether the feed has been collected. Corresponding to the interface, it may be a yellow star or a red heart.
Due to the limitations of relational databases, you need to do an aggregation on the server side, such as feeding ObjectWrap it up and generate an object like FeedWrapper
:
public class FeedWrapper { private Feed feed; private boolean marked; public boolean isMarked() { return marked; } public void setMarked(boolean marked) { this.marked = marked; } public FeedWrapper(Feed feed, boolean marked) { this.feed = feed; this.marked = marked; } }
Then define a service object like FeedService
:
public ArrayList<FeedWrapper> wrapFeed(List<Feed> markedFeeds, List<Feed> feeds) { return newArrayList(transform(feeds, new Function<Feed, FeedWrapper>() { @Override public FeedWrapper apply(Feed feed) { if (markedFeeds.contains(feed)) { return new FeedWrapper(feed, true); } else { return new FeedWrapper(feed, false); } } })); }
Okay, this can be considered a passable implementation, butstaticStrongly typed Java is a bit reluctant to do this, and once new changes occur (almost certainly will happen), we still Put this part of the logic in JavaScript and see how it simplifies the process.
Let’s get to the topic. In this article we will use lodash
as a functional programming library to simplify code writing. Since JavaScript is a dynamically weakly typed language, we can add attributes to an object at any time, so that a simple map
operation can complete the above Java corresponding code:
_.map(feeds, function(item) { return _.extend(item, {marked: isMarked(item.id)}); });
where Function isMarked
will do one thing:
var userMarkedIds = [1, 2]; function isMarked(id) { return _.includes(userMarkedIds, id); }
That is, check whether the incoming parameters are in a list userMarkedIds
. This list may be obtained by the following request :
$ curl http://www.php.cn/:9999/user/1/marked-feed-ids
The purpose of only obtaining the id is to reduce the data size of network transmission. Of course, you can also request all /marked-feeds
and then do it locally _.pluck(feeds, 'id')
to extract all id
attributes.
嗯,代码是精简了许多。但是如果仅仅能做到这一步的话,也没有多大的好处嘛。现在需求又有了变化,我们需要在另一个页面上展示当前用户的收藏夹(用以展示用户所有收藏的feed)。作为程序员,我们可不愿意重新写一套界面,如果能复用同一套逻辑当然最好了。
比如对于上面这个列表,我们已经有了对应的模板:
{{#each feeds}} <li class="list-item"> <p class="section" data-feed-id="{{this.id}}"> {{#if this.marked}} <span class="marked icon-favorite"></span> {{else}} <span class="unmarked icon-favorite"></span> {{/if}} <a href="/feeds/{{this.url}}"> <p class="detail"> <h3>{{this.title}}</h3> </p> </a> </p> </li> {{/each}}
事实上,这段代码在收藏夹页面上完全可以复用,我们只需要把所有的marked
属性都设置为true就行了!简单,很快我们就可以写出对应的代码:
_.map(feeds, function(item) { return _.extend(item, {marked: true}); });
漂亮!而且重要的是,它还可以如正常工作!但是作为程序员,你很快就发现了两处代码的相似性:
_.map(feeds, function(item) { return _.extend(item, {marked: isMarked(item.id)}); }); _.map(feeds, function(item) { return _.extend(item, {marked: true}); });
消除重复是一个有追求的程序员的基本素养,不过要消除这两处貌似有点困难:位于marked:
后边的,一个是函数调用,另一个是值!如果要简化,我们不得不做一个匿名函数,然后以回调的方式来简化:
function wrapFeeds(feeds, predicate) { return _.map(feeds, function(item) { return _.extend(item, {marked: predicate(item.id)}); }); }
对于feed列表,我们要调用:
wrapFeeds(feeds, isMarked);
而对于收藏夹,则需要传入一个匿名函数:
wrapFeeds(feeds, function(item) {return true});
在lodash
中,这样的匿名函数可以用_.wrap
来简化:
wrapFeeds(feeds, _.wrap(true));
好了,目前来看,简化的还不错,代码缩减了,而且也好读了一些(当然前提是你已经熟悉了函数式编程的读法)。
如果仔细审视isMarked
函数,会发现它对外部的依赖不是很漂亮(而且这个外部依赖是从网络异步请求来的),也就是说,我们需要在请求到markedIds
的地方才能定义isMarked
函数,这样就把函数定义绑定
到了一个固定的地方,如果该函数的逻辑比较复杂,那么势必会影响代码的可维护性(或者更糟糕的是,多出维护)。
要将这部分代码隔离出去,我们需要将ids
作为参数传递出去,并得到一个可以当做谓词(判断一个id是否在列表中的谓词)的函数。
简而言之,我们需要:
var predicate = createFunc(ids); wrapFeeds(feeds, predicate);
这里的createFunc
函数接受一个列表作为参数,并返回了一个谓词函数。而这个谓词函数就是上边说的isMarked
。这个神奇的过程被称为柯里化currying
,或者偏函数partial
。在lodash
中,这个很容易实现:
function isMarkedIn(ids) { return _.partial(_.includes, ids); }
这个函数会将ids
保存起来,当被调用时,它会被展开为:_.includes(ids, <id>)
。只不过这个<id>
会在实际迭代的时候才传入:
$('/marked-feed-ids').done(function(ids) { var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids)); console.log(wrappedFeeds); });
这样我们的代码就被简化成了:
$('/marked-feed-ids').done(function(ids) { var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids)); var markedFeeds = wrapFeeds(feeds, _.wrap(true)); allFeedList.html(template({feeds: wrappedFeeds})); markedFeedList.html(template({feeds: markedFeeds})); });
The above is the detailed content of A brief discussion on JavaScript functional programming tutorial (picture). For more information, please follow other related articles on the PHP Chinese website!