成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一個簡單的JavaScript函數式編程教程

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

前言

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

[[134858]]

和其他同事預演的時候,突然發現其實我們的主題或多或少都有些關聯,我講的部分也涉及到了基于事件的并發機制和函數式編程。仔細想想,應該與JavaScript本身的特性不無關系:

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

  2. 函數式編程使其天然支持回調,從而非常適合異步/事件機制

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

會后的第二天,我在項目代碼里忽然想要將一個聚合模型用函數式編程的方式重寫一下,結果發現思路竟然與NoSQL依稀有些聯系,進一步發現自己很多不足。

下面這個例子來自于實際項目中的場景,不過Domain做了切換,但是絲毫不影響閱讀和理解背后的機制。

一個書簽應用

設想有這樣一個應用:用戶可以看到一個訂閱的RSS的列表。列表中的每一項(稱為一個Feed),包含一個id,一個文章的標題title和一個文章的鏈接url。

數據模型看起來是這樣的:

  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構建自動監控腳本' 
  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構建前端應用' 
  16.     } 
  17. ]; 

當這個簡單應用沒有任何用戶相關的信息時,模型非常簡單。但是很快,應用需要從單機版擴展到Web版,也就是說,我們引入了用戶的概念。每個用戶都能看到一個這樣的列表。另外,用戶還可以收藏Feed。當然,收藏之后,用戶還可以查看收藏的Feed列表。

一個簡單的JavaScript函數式編程教程

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

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

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

bookmarkds design

服務器端聚合

由于關系型數據庫的限制,你需要在服務器端做一次聚合,比如將feed對象包裝一下,生成一個FeedWrapper之類的對象:

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

然后定義一個FeedService之類的服務對象:

  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.     })); 

好吧,這也算是一個還湊合的實現,但是靜態強類型的Java做這個事兒有點勉強,而且一旦發生新的變化(幾乎肯定會發生),我們還是把這部分邏輯放在JavaScript中,來看看它是如何簡化這一個過程的。

客戶端聚合

快要說到主題了,這篇文章我們會使用lodash作為函數式編程的庫來簡化代碼的編寫。由于JavaScript是一個動態弱類型的語言,我們可以隨時為一個對象添加屬性,這樣一個簡單的map操作就可以完成上邊的Java對應的代碼了:

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

即查看傳入的參數是否在一個列表userMarkedIds,這個列表可能由下列的請求來獲得:

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

之所有只獲取id是為了減少網絡傳輸的數據大小,當然你也可以將全部的/marked-feeds都請求到,然后在本地做_.pluck(feeds, 'id')來抽取所有的id屬性。

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

比如對于上面這個列表,我們已經有了對應的模板:

  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}} 

事實上,這段代碼在收藏夾頁面上完全可以復用,我們只需要把所有的marked屬性都設置為true就行了!簡單,很快我們就可以寫出對應的代碼:

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

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

  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. 消除重復是一個有追求的程序員的基本素養,不過要消除這兩處貌似有點困難:位于marked:后邊的,一個是函數調用,另一個是值!如果要簡化,我們不得不做一個匿名函數,然后以回調的方式來簡化: 
  10.  
  11. function wrapFeeds(feeds, predicate) { 
  12.     return _.map(feeds, function(item) { 
  13.         return _.extend(item, {marked: predicate(item.id)}); 
  14.     }); 

對于feed列表,我們要調用:

wrapFeeds(feeds, isMarked);

而對于收藏夾,則需要傳入一個匿名函數:

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

lodash中,這樣的匿名函數可以用_.wrap來簡化:

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

好了,目前來看,簡化的還不錯,代碼縮減了,而且也好讀了一些(當然前提是你已經熟悉了函數式編程的讀法)。

更進一步

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

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

簡而言之,我們需要:

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

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

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

這個函數會將ids保存起來,當被調用時,它會被展開為:_.includes(ids, <id>)。只不過這個<id>會在實際迭代的時候才傳入:

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

這樣我們的代碼就被簡化成了:

  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. }); 
 
責任編輯:王雪燕 來源: icodeit
相關推薦

2012-08-23 14:23:33

函數式編程

2016-08-11 10:11:07

JavaScript函數編程

2017-03-22 11:22:04

JavaScript函數式編程

2010-06-22 13:32:26

函數式編程JavaScript

2018-09-18 10:11:21

前端vue.jsjavascript

2016-08-11 10:34:37

Javascript函數編程

2019-08-06 09:00:00

JavaScript函數式編程前端

2017-10-26 08:53:38

前端JavaScript函數式編程

2021-02-05 16:03:48

JavaScript游戲學習前端

2012-03-21 09:30:11

ibmdw

2013-03-04 09:47:08

Python函數式編程

2009-06-22 13:43:01

F#函數式編程

2020-09-23 16:07:52

JavaScript函數柯里化

2024-05-13 08:40:02

Go事件驅動編程

2022-10-21 14:21:46

JavaScript筆記技能

2009-06-09 21:50:55

Javascript函數getStyle

2011-03-24 09:34:41

SPRING

2009-06-23 14:08:00

Java Socket

2021-01-05 12:38:53

C++編程語言軟件開發

2021-04-14 07:33:02

Java函數式斷言
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日本不卡高字幕在线2019 | 成人a视频在线观看 | 91在线视频一区 | 国产精品免费一区二区三区四区 | 国产精品3区 | 国产精品美女久久久久久久久久久 | 欧美久久久久久久 | 国产精品欧美一区二区三区不卡 | 国产午夜精品一区二区三区四区 | 8x国产精品视频一区二区 | 99热精品久久 | 成人精品国产免费网站 | 精品少妇一区二区三区在线播放 | 国产99久久精品一区二区300 | 日日夜夜精品免费视频 | 成年人在线视频 | 国产欧美日韩一区 | 精品亚洲永久免费精品 | 欧美一区在线视频 | 在线观看国产视频 | 国产一区二区精品在线 | 热久久免费视频 | 欧美a在线| 亚洲精品国产电影 | 精品成人av | 超碰人人做 | 亚洲综合一区二区三区 | 日韩一区二区三区在线播放 | 国产成人jvid在线播放 | 亚洲欧美精品一区 | 射久久| 自拍偷拍亚洲欧美 | 亚洲免费在线播放 | 欧美日高清视频 | 爱高潮www亚洲精品 中文字幕免费视频 | 日韩在线一区二区三区 | 亚洲精品在线观看网站 | 狠狠操狠狠干 | 日本精品一区二区 | 国产精品久久久久av | 国产精品嫩草影院精东 |