Home > Web Front-end > JS Tutorial > A brief discussion on JavaScript functional programming tutorial (picture)

A brief discussion on JavaScript functional programming tutorial (picture)

黄舟
Release: 2017-03-13 17:12:39
Original
1224 people have browsed it

Preface

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:

  1. Event-Based Node.js is very typical in concurrency A model

  2. Functional programming makes it naturally support callbacks, making it very suitable for asynchronous/event mechanisms

  3. 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.

A bookmark application

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构建前端应用'
    }
];
Copy after login

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.

feed and user

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
Copy after login

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.

bookmarkds design

ServiceServer-side aggregation

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;
    }
}
Copy after login

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);
            }
        }
    }));
}
Copy after login

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.

Client-side aggregation

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)});
});
Copy after login

where Function isMarked will do one thing:

var userMarkedIds = [1, 2];
function isMarked(id) {
    return _.includes(userMarkedIds, id);
}
Copy after login

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
Copy after login

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}}
Copy after login

事实上,这段代码在收藏夹页面上完全可以复用,我们只需要把所有的marked属性都设置为true就行了!简单,很快我们就可以写出对应的代码:

_.map(feeds, function(item) {
    return _.extend(item, {marked: true});
});
Copy after login

漂亮!而且重要的是,它还可以如正常工作!但是作为程序员,你很快就发现了两处代码的相似性:

_.map(feeds, function(item) {
    return _.extend(item, {marked: isMarked(item.id)});
});

_.map(feeds, function(item) {
    return _.extend(item, {marked: true});
});
Copy after login

消除重复是一个有追求的程序员的基本素养,不过要消除这两处貌似有点困难:位于marked:后边的,一个是函数调用,另一个是值!如果要简化,我们不得不做一个匿名函数,然后以回调的方式来简化:

function wrapFeeds(feeds, predicate) {
    return _.map(feeds, function(item) {
        return _.extend(item, {marked: predicate(item.id)});
    });
}
Copy after login

对于feed列表,我们要调用:

wrapFeeds(feeds, isMarked);
Copy after login

而对于收藏夹,则需要传入一个匿名函数:

wrapFeeds(feeds, function(item) {return true});
Copy after login

lodash中,这样的匿名函数可以用_.wrap来简化:

wrapFeeds(feeds, _.wrap(true));
Copy after login

好了,目前来看,简化的还不错,代码缩减了,而且也好读了一些(当然前提是你已经熟悉了函数式编程的读法)。

更进一步

如果仔细审视isMarked函数,会发现它对外部的依赖不是很漂亮(而且这个外部依赖是从网络异步请求来的),也就是说,我们需要在请求到markedIds的地方才能定义isMarked函数,这样就把函数定义绑定到了一个固定的地方,如果该函数的逻辑比较复杂,那么势必会影响代码的可维护性(或者更糟糕的是,多出维护)。

要将这部分代码隔离出去,我们需要将ids作为参数传递出去,并得到一个可以当做谓词(判断一个id是否在列表中的谓词)的函数。

简而言之,我们需要:

var predicate = createFunc(ids);
wrapFeeds(feeds, predicate);
Copy after login

这里的createFunc函数接受一个列表作为参数,并返回了一个谓词函数。而这个谓词函数就是上边说的isMarked。这个神奇的过程被称为柯里化currying,或者偏函数partial。在lodash中,这个很容易实现:

function isMarkedIn(ids) {
    return _.partial(_.includes, ids);
}
Copy after login

这个函数会将ids保存起来,当被调用时,它会被展开为:_.includes(ids, <id>)。只不过这个<id>会在实际迭代的时候才传入:

$(&#39;/marked-feed-ids&#39;).done(function(ids) {
    var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids));
    console.log(wrappedFeeds);
});
Copy after login

这样我们的代码就被简化成了:

$(&#39;/marked-feed-ids&#39;).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}));
});
Copy after login













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!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template