ホームページ > 記事 > ウェブフロントエンド > JavaScript 関数プログラミング チュートリアルに関する簡単な説明 (写真)
4月初旬に私が北京にいたとき、クラスメートの徐昊は、会社の同僚が書いた記事は単純すぎて、細部に注意を払いすぎていると言ったので、私はゴマを拾い、スイカをなくしてしまいました。ブログの更新をやめてしまいました(実はプロジェクトが多忙のため全く更新していませんでした)。先週、私は他の数人の同僚と一緒に「Martin Fowler Shenzhen Tour」イベントに参加し、同僚の Tashi と、Yang Yun (江湖の大悪魔として知られています) と一緒に「FullStack Language JavaScript」を投稿しました。テーマは「マスター関数スタイルのプログラミングと制御システムの複雑さ」、Li Xin (江湖語ではXinyeとして知られています) のテーマは「同時実行性: 過去と死後」です。
他の同僚とリハーサルをしているときに、私たちのトピックが多かれ少なかれ関連していることに突然気づきました。私が話した部分には、イベントベースの同時実行メカニズムと関数型プログラミングも含まれていました。よく考えてみると、これは JavaScript 自体の特性に関連しているはずです。
イベントベース Node.js は同時実行における非常に典型的なモデルです
関数型プログラミングによりそれが可能になります 当然コールバックをサポートします非同期/イベントメカニズムに非常に適しています
関数型プログラミング機能により、DSLの作成に非常に適しています
会議の翌日、突然プロジェクトコードに集計モデルを追加したくなりました書き直しました関数型プログラミングを使用してみたところ、そのアイデアが NoSQL と漠然と関連していることがわかり、さらに自分には多くの欠点があることがわかりました。
次の例は実際のプロジェクトのシーンからのものですが、ドメインが切り替えられますが、その背後にあるメカニズムの読み取りと理解にはまったく影響しません。
ユーザーが購読した RSS のリストを表示できるアプリケーションを想像してください。リスト内の各項目 (フィードと呼ばれます) には、id
、記事のタイトル title
、および記事へのリンク url
が含まれています。 id
,一个文章的标题title
和一个文章的链接url
。
数据模型看起来是这样的:
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构建前端应用' } ];
当这个简单应用没有任何用户相关的信息时,模型非常简单。但是很快,应用需要从单机版扩展到Web版,也就是说,我们引入了用户的概念。每个用户都能看到一个这样的列表。另外,用户还可以收藏Feed。当然,收藏之后,用户还可以查看收藏的Feed列表。
由于每个用户可以收藏多个Feed,而每个Feed也可以被多个用户收藏,因此它们之间的多对多关系如上图所示。可能你还会想到诸如:
$ curl http://www.php.cn/:9999/user/1/feeds
来获取用户1
的所有feed
等,但是这些都不重要,真正的问题是,当你拿到了所有Feed之后,在UI上,需要为每个Feed填加一个属性makred
。这个属性用来标示该feed是否已经被收藏了。对应到界面上,可能是一枚黄色的星星,或者一个红色的心。
由于关系型数据库的限制,你需要在服务器端做一次聚合,比如将feed对象包装一下,生成一个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; } }
然后定义一个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); } } })); }
好吧,这也算是一个还凑合的实现,但是静态强类型的Java做这个事儿有点勉强,而且一旦发生新的变化(几乎肯定会发生),我们还是把这部分逻辑放在JavaScript中,来看看它是如何简化这一个过程的。
快要说到主题了,这篇文章我们会使用lodash
作为函数式编程的库来简化代码的编写。由于JavaScript是一个动态弱类型的语言,我们可以随时为一个对象添加属性,这样一个简单的map
操作就可以完成上边的Java对应的代码了:
_.map(feeds, function(item) { return _.extend(item, {marked: isMarked(item.id)}); });
其中函数isMarked
会做这样一件事儿:
var userMarkedIds = [1, 2]; function isMarked(id) { return _.includes(userMarkedIds, id); }
即查看传入的参数是否在一个列表userMarkedIds
,这个列表可能由下列的请求来获得:
$ curl http://www.php.cn/:9999/user/1/marked-feed-ids
之所有只获取id是为了减少网络传输的数据大小,当然你也可以将全部的/marked-feeds
都请求到,然后在本地做_.pluck(feeds, 'id')
来抽取所有的id
{{#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}}🎜 この単純なアプリケーションがユーザー関連の情報を持たない場合、モデルは非常に単純になります。しかしすぐに、アプリケーションをスタンドアロン バージョンから Web バージョンに拡張する必要がありました。つまり、ユーザーの概念を導入しました。すべてのユーザーはそのようなリストを 1 つ表示できます。さらに、ユーザーはフィードを収集することもできます。もちろん、収集後は収集したフィードの一覧を確認することもできます。 🎜🎜🎜🎜各ユーザーは複数を収集できるため、フィードは複数のユーザーによって収集され、各フィードは複数のユーザーによって収集されることもあるため、それらの間の多対多の関係は上の図のようになります。ユーザー
1
のすべての feed
を取得するための 🎜_.map(feeds, function(item) { return _.extend(item, {marked: true}); });🎜 なども考えられますが、本当の問題は、いつ取得するかということです。すべてのフィード その後、UI で 属性🎜
makred を追加する必要があります。コード>。この属性は、フィードが収集されたかどうかを示すために使用されます。インターフェースに応じて、黄色の星または赤いハートになります。 🎜🎜<img src="https://img.php.cn/upload/article/000/000/194/7e61e8d04626c3521e0f324d1028bc02-1.png" alt="JavaScript 関数プログラミング チュートリアルに関する簡単な説明 (写真)">🎜<h4><a href="http://www.php.cn/js/js-jspopular-guide-service.html" target="_blank">サービス🎜サーバー側集約</a></h4>🎜リレーショナル データベースの制限により、次のことが必要です。集約を実行します。たとえば、フィード <a href="http://www.php.cn/wiki/60.html" target="_blank"> オブジェクト 🎜 をラップして <code>FeedWrapper
を生成します。クラス オブジェクト: 🎜_.map(feeds, function(item) { return _.extend(item, {marked: isMarked(item.id)}); }); _.map(feeds, function(item) { return _.extend(item, {marked: true}); });🎜 次に、
FeedService
のようなサービス オブジェクトを定義します: 🎜function wrapFeeds(feeds, predicate) { return _.map(feeds, function(item) { return _.extend(item, {marked: predicate(item.id)}); }); }🎜 これはまあまあの実装と考えられますが、静的 🎜 厳密に型指定された Java はこれを行うことに少し消極的で、新しい変更が発生すると (ほぼ確実に発生します)、ロジックのこの部分を JavaScript に組み込みます。見てみましょうこのプロセスがいかに簡素化されるか。 🎜
lodash
を使用します。 JavaScript は動的に弱い型指定の言語であるため、いつでもオブジェクトに属性を追加できます。このようにして、単純な map
操作で、上記の Java 対応コードを完成させることができます。 >isMarked は 1 つのことを行います: 🎜wrapFeeds(feeds, isMarked);🎜 つまり、受信パラメータがリスト
userMarkedIds
にあるかどうかを確認します。このリストは次のリクエストによって取得できます: 🎜wrapFeeds(feeds, function(item) {return true});🎜 取得のみ。もちろん、すべての
/marked-feeds
をリクエストしてから _.pluck(feeds, 'id ')
を実行することもできます。 > すべての id
属性を抽出します。 🎜嗯,代码是精简了许多。但是如果仅仅能做到这一步的话,也没有多大的好处嘛。现在需求又有了变化,我们需要在另一个页面上展示当前用户的收藏夹(用以展示用户所有收藏的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>
。只不过这个<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})); });
以上がJavaScript 関数プログラミング チュートリアルに関する簡単な説明 (写真)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。