Maison >interface Web >js tutoriel >Une brève discussion sur le didacticiel de programmation fonctionnelle JavaScript (photo)

Une brève discussion sur le didacticiel de programmation fonctionnelle JavaScript (photo)

黄舟
黄舟original
2017-03-13 17:12:391236parcourir

Avant-propos

Lorsque j'étais à Pékin début avril, mon camarade de classe Xu Hao a dit que les articles écrits par des collègues de notre entreprise étaient trop simples et accordaient trop d'attention aux détails. Ensuite, j'ai ramassé les graines de sésame. et j'ai perdu la pastèque, donc je n'ai plus mis à jour le blog (en fait, la cause première est que le projet est trop chargé). La semaine dernière, j'ai participé à l'événement "Martin Fowler Shenzhen Tour" avec plusieurs autres collègues. Mon collègue Tashi et moi avons contribué à un "FullStack Language JavaScript", avec Yang Yun (connu comme le grand diable du Jianghu). ) ) est « Maîtriser la programmation fonctionnelle et contrôler la complexité du système », et celui de Li Xin (connu sous le nom de Xin Ye dans le Jianghu) est « Concurrence : passé et au-delà ».

En répétant avec d'autres collègues, j'ai soudain découvert que nos sujets sont plus ou moins liés. La partie dont j'ai parlé impliquait également des mécanismes de concurrence basés sur les événements et de la programmation fonctionnelle. Si vous y réfléchissez bien, cela devrait être lié aux caractéristiques de JavaScript lui-même :

  1. Basé sur les événements Node.js est très typique en concurrence Un modèle de

  2. La programmation fonctionnelle le rend naturellement compatible avec les rappels, ce qui le rend très approprié pour les mécanismes asynchrones/événementiels

  3. Les fonctionnalités de programmation fonctionnelle le rendent très adapté à l'écriture DSL

Le lendemain de la réunion, j'ai soudain eu envie de réécrire un modèle d'agrégation en utilisant la programmation fonctionnelle dans le code du projet, il s'est avéré que l'idée était vaguement liée à NoSQL, et j'ai découvert en outre que j'avais de nombreuses lacunes.

L'exemple suivant provient d'une scène d'un projet réel, mais le domaine a été changé, mais cela n'affecte pas du tout la lecture et la compréhension du mécanisme qui se cache derrière.

Une application de favoris

Imaginez une application où les utilisateurs peuvent voir une liste des RSS abonnés. Chaque élément de la liste (appelé flux) contient un id, un titre d'article title et un lien vers l'article url.

Le modèle de données ressemble à ceci :

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

Le modèle est très simple lorsque cette application simple ne dispose d'aucune information relative à l'utilisateur. Mais très vite, il a fallu étendre l'application de la version autonome à la version Web. Autrement dit, nous avons introduit la notion d'utilisateurs. Chaque utilisateur peut voir une de ces listes. De plus, les utilisateurs peuvent également collecter des flux. Bien entendu, après la collecte, les utilisateurs peuvent également consulter la liste des flux collectés.

feed and user

Étant donné que chaque utilisateur peut collecter plusieurs flux et que chaque flux peut également être collecté par plusieurs utilisateurs, la relation plusieurs-à-plusieurs entre eux est celle indiquée dans la figure ci-dessus. . Vous pouvez également penser à des choses comme :

$ curl http://www.php.cn/:9999/user/1/feeds

pour obtenir tous les 1 utilisateurs feed, etc., mais ce n'est pas important. Le vrai problème est qu'une fois que vous avez obtenu tous les flux, dans l'interface utilisateur ci-dessus, vous devez ajouter un attribut makred à chaque flux. Cet attribut est utilisé pour indiquer si le flux a été collecté. Correspondant à l'interface, il peut s'agir d'une étoile jaune ou d'un cœur rouge.

bookmarkds design

ServiceAgrégation côté serveur

En raison des limitations des bases de données relationnelles, vous devez effectuer une agrégation côté serveur , comme nourrir Enveloppez l'objet pour générer un objet comme 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;
    }
}

puis définissez un objet de service comme 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);
            }
        }
    }));
}

D'accord, cela peut être considéré comme une implémentation passable, mais statiqueJava fortement typé est un peu réticent à le faire, et une fois que de nouveaux changements se produiront (ils se produiront presque certainement), nous devons encore mettre cette partie de la logique Mettez-le en JavaScript et voyez comment cela simplifie ce processus.

Agrégation côté client

Passons au sujet. Dans cet article, nous utiliserons lodash comme bibliothèque de programmation fonctionnelle pour simplifier l'écriture de code. Puisque JavaScript est un langage dynamiquement faiblement typé, nous pouvons ajouter des attributs à un objet à tout moment, de cette façon, une simple opération map peut compléter le code Java correspondant ci-dessus :

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

où la fonction. isMarked fera quelque chose comme ceci :

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

Autrement dit, vérifiez si les paramètres entrants sont dans une liste userMarkedIds Cette liste peut être obtenue par la requête suivante :

$ curl http://www.php.cn/:9999/user/1/marked-feed-ids
.

Le but d'obtenir uniquement l'identifiant est de réduire la taille des données de transmission réseau. Bien sûr, vous pouvez également demander tous les /marked-feeds puis faire _.pluck(feeds, 'id') localement pour extraire tous les id attributs.

嗯,代码是精简了许多。但是如果仅仅能做到这一步的话,也没有多大的好处嘛。现在需求又有了变化,我们需要在另一个页面上展示当前用户的收藏夹(用以展示用户所有收藏的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, 53384f78b45ee9f1e3082cf378b9c5b4)。只不过这个53384f78b45ee9f1e3082cf378b9c5b4会在实际迭代的时候才传入:

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

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

$(&#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}));
});













Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn