自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一個(gè)簡(jiǎn)單的JavaScript函數(shù)式編程教程

開發(fā) 前端
4月初在北京的時(shí)候,徐昊同學(xué)表示我們公司的同事們寫的文章都太簡(jiǎn)單,太注重細(xì)節(jié),然后撿起了芝麻丟了西瓜,于是我就不再更新博客(其實(shí)根本原因是 項(xiàng)目太忙)。上周和其他幾個(gè)同事一起參加“Martin Fowler深圳行”的活動(dòng),我和同事扎西貢獻(xiàn)了一個(gè)《FullStack Language JavaScript》,一起的還有楊云(江湖人稱大魔頭)的話題是《掌握函數(shù)式編程,控制系統(tǒng)復(fù)雜度》,李新(江湖人稱新爺)的話題是《并發(fā):前生來 世》。

前言

4月初在北京的時(shí)候,徐昊同學(xué)表示我們公司的同事們寫的文章都太簡(jiǎn)單,太注重細(xì)節(jié),然后撿起了芝麻丟了西瓜,于是我就不再更新博客(其實(shí)根本原因是 項(xiàng)目太忙)。上周和其他幾個(gè)同事一起參加“Martin Fowler深圳行”的活動(dòng),我和同事扎西貢獻(xiàn)了一個(gè)《FullStack Language JavaScript》,一起的還有楊云(江湖人稱大魔頭)的話題是《掌握函數(shù)式編程,控制系統(tǒng)復(fù)雜度》,李新(江湖人稱新爺)的話題是《并發(fā):前生來 世》。

[[134858]]

和其他同事預(yù)演的時(shí)候,突然發(fā)現(xiàn)其實(shí)我們的主題或多或少都有些關(guān)聯(lián),我講的部分也涉及到了基于事件的并發(fā)機(jī)制和函數(shù)式編程。仔細(xì)想想,應(yīng)該與JavaScript本身的特性不無關(guān)系:

  1. 基于事件(Event-Based)的Node.js的正是并發(fā)中很典型的一個(gè)模型

  2. 函數(shù)式編程使其天然支持回調(diào),從而非常適合異步/事件機(jī)制

  3. 函數(shù)式編程特性使其非常適合DSL的編寫

會(huì)后的第二天,我在項(xiàng)目代碼里忽然想要將一個(gè)聚合模型用函數(shù)式編程的方式重寫一下,結(jié)果發(fā)現(xiàn)思路竟然與NoSQL依稀有些聯(lián)系,進(jìn)一步發(fā)現(xiàn)自己很多不足。

下面這個(gè)例子來自于實(shí)際項(xiàng)目中的場(chǎng)景,不過Domain做了切換,但是絲毫不影響閱讀和理解背后的機(jī)制。

一個(gè)書簽應(yīng)用

設(shè)想有這樣一個(gè)應(yīng)用:用戶可以看到一個(gè)訂閱的RSS的列表。列表中的每一項(xiàng)(稱為一個(gè)Feed),包含一個(gè)id,一個(gè)文章的標(biāo)題title和一個(gè)文章的鏈接url。

數(shù)據(jù)模型看起來是這樣的:

  1. var feeds = [ 
  2.     { 
  3.         'id': 1, 
  4.         'url''http://abruzzi.github.com/2015/03/list-comprehension-in-python/'
  5.         'title''Python中的 list comprehension 以及 generator' 
  6.     }, 
  7.     { 
  8.         'id': 2, 
  9.         'url''http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/'
  10.         'title''使用inotify/fswatch構(gòu)建自動(dòng)監(jiān)控腳本' 
  11.     }, 
  12.     { 
  13.         'id': 3, 
  14.         'url''http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/'
  15.         'title''使用underscore.js構(gòu)建前端應(yīng)用' 
  16.     } 
  17. ]; 

當(dāng)這個(gè)簡(jiǎn)單應(yīng)用沒有任何用戶相關(guān)的信息時(shí),模型非常簡(jiǎn)單。但是很快,應(yīng)用需要從單機(jī)版擴(kuò)展到Web版,也就是說,我們引入了用戶的概念。每個(gè)用戶都能看到一個(gè)這樣的列表。另外,用戶還可以收藏Feed。當(dāng)然,收藏之后,用戶還可以查看收藏的Feed列表。

一個(gè)簡(jiǎn)單的JavaScript函數(shù)式編程教程

由于每個(gè)用戶可以收藏多個(gè)Feed,而每個(gè)Feed也可以被多個(gè)用戶收藏,因此它們之間的多對(duì)多關(guān)系如上圖所示??赡苣氵€會(huì)想到諸如

  1. $ curl http://localhost:9999/user/1/feeds 

來獲取用戶1的所有feed等,但是這些都不重要,真正的問題是,當(dāng)你拿到了所有Feed之后,在UI上,需要為每個(gè)Feed填加一個(gè)屬性makred。這個(gè)屬性用來標(biāo)示該feed是否已經(jīng)被收藏了。對(duì)應(yīng)到界面上,可能是一枚黃色的星星,或者一個(gè)紅色的心。

bookmarkds design

服務(wù)器端聚合

由于關(guān)系型數(shù)據(jù)庫的限制,你需要在服務(wù)器端做一次聚合,比如將feed對(duì)象包裝一下,生成一個(gè)FeedWrapper之類的對(duì)象:

  1. public class FeedWrapper { 
  2.     private Feed feed; 
  3.     private boolean marked; 
  4.  
  5.     public boolean isMarked() { 
  6.         return marked; 
  7.     } 
  8.  
  9.     public void setMarked(boolean marked) { 
  10.         this.marked = marked; 
  11.     } 
  12.  
  13.     public FeedWrapper(Feed feed, boolean marked) { 
  14.         this.feed = feed; 
  15.         this.marked = marked; 
  16.     } 

然后定義一個(gè)FeedService之類的服務(wù)對(duì)象:

  1. public ArrayList<FeedWrapper> wrapFeed(List<Feed> markedFeeds, List<Feed> feeds) { 
  2.     return newArrayList(transform(feeds, new Function<Feed, FeedWrapper>() { 
  3.         @Override 
  4.         public FeedWrapper apply(Feed feed) { 
  5.             if (markedFeeds.contains(feed)) { 
  6.                 return new FeedWrapper(feed, true); 
  7.             } else { 
  8.                 return new FeedWrapper(feed, false); 
  9.             } 
  10.         } 
  11.     })); 

好吧,這也算是一個(gè)還湊合的實(shí)現(xiàn),但是靜態(tài)強(qiáng)類型的Java做這個(gè)事兒有點(diǎn)勉強(qiáng),而且一旦發(fā)生新的變化(幾乎肯定會(huì)發(fā)生),我們還是把這部分邏輯放在JavaScript中,來看看它是如何簡(jiǎn)化這一個(gè)過程的。

客戶端聚合

快要說到主題了,這篇文章我們會(huì)使用lodash作為函數(shù)式編程的庫來簡(jiǎn)化代碼的編寫。由于JavaScript是一個(gè)動(dòng)態(tài)弱類型的語言,我們可以隨時(shí)為一個(gè)對(duì)象添加屬性,這樣一個(gè)簡(jiǎn)單的map操作就可以完成上邊的Java對(duì)應(yīng)的代碼了:

  1. _.map(feeds, function(item) { 
  2.     return _.extend(item, {marked: isMarked(item.id)}); 
  3. }); 
  4.  
  5. 其中函數(shù)isMarked會(huì)做這樣一件事兒: 
  6.  
  7. var userMarkedIds = [1, 2]; 
  8. function isMarked(id) { 
  9.     return _.includes(userMarkedIds, id); 

即查看傳入的參數(shù)是否在一個(gè)列表userMarkedIds,這個(gè)列表可能由下列的請(qǐng)求來獲得:

$ curl http://localhost:9999/user/1/marked-feed-ids

之所有只獲取id是為了減少網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)大小,當(dāng)然你也可以將全部的/marked-feeds都請(qǐng)求到,然后在本地做_.pluck(feeds, 'id')來抽取所有的id屬性。

嗯,代碼是精簡(jiǎn)了許多。但是如果僅僅能做到這一步的話,也沒有多大的好處嘛?,F(xiàn)在需求又有了變化,我們需要在另一個(gè)頁面上展示當(dāng)前用戶的收藏夾(用以展示用戶所有收藏的feed)。作為程序員,我們可不愿意重新寫一套界面,如果能復(fù)用同一套邏輯當(dāng)然最好了。

比如對(duì)于上面這個(gè)列表,我們已經(jīng)有了對(duì)應(yīng)的模板:

  1. {{#each feeds}} 
  2. <li class="list-item"
  3.     <div class="section" data-feed-id="{{this.id}}"
  4.         {{#if this.marked}} 
  5.             <span class="marked icon-favorite"></span> 
  6.         {{else}} 
  7.             <span class="unmarked icon-favorite"></span> 
  8.         {{/if}} 
  9.         <a href="/feeds/{{this.url}}"
  10.             <div class="detail"
  11.                 <h3>{{this.title}}</h3> 
  12.             </div> 
  13.         </a> 
  14.     </div> 
  15. </li> 
  16. {{/each}} 

事實(shí)上,這段代碼在收藏夾頁面上完全可以復(fù)用,我們只需要把所有的marked屬性都設(shè)置為true就行了!簡(jiǎn)單,很快我們就可以寫出對(duì)應(yīng)的代碼:

  1. _.map(feeds, function(item) { 
  2.     return _.extend(item, {marked: true}); 
  3. }); 

漂亮!而且重要的是,它還可以如正常工作!但是作為程序員,你很快就發(fā)現(xiàn)了兩處代碼的相似性:

  1. _.map(feeds, function(item) { 
  2.     return _.extend(item, {marked: isMarked(item.id)}); 
  3. }); 
  4.  
  5. _.map(feeds, function(item) { 
  6.     return _.extend(item, {marked: true}); 
  7. }); 
  8.  
  9. 消除重復(fù)是一個(gè)有追求的程序員的基本素養(yǎng),不過要消除這兩處貌似有點(diǎn)困難:位于marked:后邊的,一個(gè)是函數(shù)調(diào)用,另一個(gè)是值!如果要簡(jiǎn)化,我們不得不做一個(gè)匿名函數(shù),然后以回調(diào)的方式來簡(jiǎn)化: 
  10.  
  11. function wrapFeeds(feeds, predicate) { 
  12.     return _.map(feeds, function(item) { 
  13.         return _.extend(item, {marked: predicate(item.id)}); 
  14.     }); 

對(duì)于feed列表,我們要調(diào)用:

wrapFeeds(feeds, isMarked);

而對(duì)于收藏夾,則需要傳入一個(gè)匿名函數(shù):

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

lodash中,這樣的匿名函數(shù)可以用_.wrap來簡(jiǎn)化:

wrapFeeds(feeds, _.wrap(true));

好了,目前來看,簡(jiǎn)化的還不錯(cuò),代碼縮減了,而且也好讀了一些(當(dāng)然前提是你已經(jīng)熟悉了函數(shù)式編程的讀法)。

更進(jìn)一步

如果仔細(xì)審視isMarked函數(shù),會(huì)發(fā)現(xiàn)它對(duì)外部的依賴不是很漂亮(而且這個(gè)外部依賴是從網(wǎng)絡(luò)異步請(qǐng)求來的),也就是說,我們需要在請(qǐng)求到markedIds的地方才能定義isMarked函數(shù),這樣就把函數(shù)定義綁定到了一個(gè)固定的地方,如果該函數(shù)的邏輯比較復(fù)雜,那么勢(shì)必會(huì)影響代碼的可維護(hù)性(或者更糟糕的是,多出維護(hù))。

要將這部分代碼隔離出去,我們需要將ids作為參數(shù)傳遞出去,并得到一個(gè)可以當(dāng)做謂詞(判斷一個(gè)id是否在列表中的謂詞)的函數(shù)。

簡(jiǎn)而言之,我們需要:

  1. var predicate = createFunc(ids); 
  2. wrapFeeds(feeds, predicate); 

這里的createFunc函數(shù)接受一個(gè)列表作為參數(shù),并返回了一個(gè)謂詞函數(shù)。而這個(gè)謂詞函數(shù)就是上邊說的isMarked。這個(gè)神奇的過程被稱為柯里化currying,或者偏函數(shù)partial。在lodash中,這個(gè)很容易實(shí)現(xiàn):

  1. function isMarkedIn(ids) { 
  2.     return _.partial(_.includes, ids); 

這個(gè)函數(shù)會(huì)將ids保存起來,當(dāng)被調(diào)用時(shí),它會(huì)被展開為:_.includes(ids, <id>)。只不過這個(gè)<id>會(huì)在實(shí)際迭代的時(shí)候才傳入:

  1. $('/marked-feed-ids').done(function(ids) { 
  2.     var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids)); 
  3.     console.log(wrappedFeeds); 
  4. }); 

這樣我們的代碼就被簡(jiǎn)化成了:

  1. $('/marked-feed-ids').done(function(ids) { 
  2.     var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids)); 
  3.     var markedFeeds = wrapFeeds(feeds, _.wrap(true)); 
  4.  
  5.     allFeedList.html(template({feeds: wrappedFeeds})); 
  6.     markedFeedList.html(template({feeds: markedFeeds})); 
  7. }); 
 
責(zé)任編輯:王雪燕 來源: icodeit
相關(guān)推薦

2012-08-23 14:23:33

函數(shù)式編程

2016-08-11 10:11:07

JavaScript函數(shù)編程

2010-06-22 13:32:26

函數(shù)式編程JavaScript

2017-03-22 11:22:04

JavaScript函數(shù)式編程

2016-08-11 10:34:37

Javascript函數(shù)編程

2018-09-18 10:11:21

前端vue.jsjavascript

2019-08-06 09:00:00

JavaScript函數(shù)式編程前端

2017-10-26 08:53:38

前端JavaScript函數(shù)式編程

2021-02-05 16:03:48

JavaScript游戲?qū)W習(xí)前端

2012-03-21 09:30:11

ibmdw

2009-06-22 13:43:01

F#函數(shù)式編程

2013-03-04 09:47:08

Python函數(shù)式編程

2020-09-23 16:07:52

JavaScript函數(shù)柯里化

2009-06-09 21:50:55

Javascript函數(shù)getStyle

2022-10-21 14:21:46

JavaScript筆記技能

2024-05-13 08:40:02

Go事件驅(qū)動(dòng)編程

2009-06-23 14:08:00

Java Socket

2010-01-07 10:04:18

F#函數(shù)式編程

2021-01-05 12:38:53

C++編程語言軟件開發(fā)

2011-03-24 09:34:41

SPRING
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)