咱們再來創建另外一種類型的應用,他的工做方式差很少,都是用函數式編程來響應狀態變化。 可是這回應用不會依賴於事件監聽。 javascript
來想象一下,你在一個新聞媒體公司工做,你的老闆讓你創建一個web應用來跟蹤競選日的政府競選結果。 數據會根據地方選區的結果持續流動,因此顯示在頁面上的結果是具備響應式特徵的。然而咱們還需跟蹤每個區域的結果, 因此會有多個對象須要追蹤。 html
咱們與其創建一個巨大的面向對象的層次結構來爲接口建模,不如把它描述爲聲明式的不可變數據。 咱們能夠把它轉換爲純函數或者半純函數,半純函數在必須保持的狀態要更新時才產生反作用(理想情況下不會不少)。 java
咱們將使用Bacon.js庫,它能夠快速開發函數式響應式編程(FRP)的應用。這個應用只在選舉日使用, 咱們的老闆認爲它花費的時間應該與之成比例。經過函數式編程和像Bacon.js這樣的庫,咱們將僅須要一半的時間。 web
不過首先咱們須要一些對象來描述選區,好比州、省、區等等。ajax
function Region(name, percent, parties) { // 可變屬性: this.name = name; this.percent = percent; // % of precincts reported this.parties = parties; // political parties // 返回一段HTML this.render = function() { var lis = this.parties.map(function(p) { return '' + p.name + ': ' + p.votes + ''; }); var output = '' + this.name + ''; output += '' + lis.join('') + ''; output += 'Percent reported: ' + this.percent; return output; } } function getRegions(data) { return JSON.parse(data).map(function(obj) { return new Region(obj.name, obj.percent, obj.parties); }); } var url = 'http://api.server.com/election-data?format=json'; var data = jQuery.ajax(url); var regions = getRegions(data); app.container.innerHTML = regions.map(function(r) { return r.render(); }).join('');
上面的代碼能夠知足對靜態選舉結果列表的展現,然而咱們須要一種動態更新選區的方式。是時候來點Bacon和FRP了。 編程
bacon有個函數,Bacon.fromPoll(),用於建立事件流,它讓事件成爲在必定時間間隔被調用的函數。 還有stream.subscribe()函數讓咱們能夠訂閱(subsribe)一個針對這個流的處理函數。因爲它是惰性的, 若是沒有訂閱者(subscriber)流實際上就不去作任何事情。 json
var eventStream = Bacon.fromPoll(10000, function() { return Bacon.Next; }); var subscriber = eventStream.subscribe(function() { var url = 'http://api.server.com/election-data?format=json'; var data = jQuery.ajax(url); var newRegions = getRegions(data); container.innerHTML = newRegions.map(function(r) { return r.render(); }).join(''); });
大致上就是把它放到一個每10秒運行一次的循環中,咱們的工做完成了。可是這個方法將會不斷的ping網絡, 而且很是低效。這還不是很函數式。讓咱們繼續深刻挖掘一下Bacon.js庫。 api
Beacon裏有EventStreams和Properties參數。Properties能夠想做是「魔術」變量,它隨着事件的響應不斷變化。 實際上這不是魔術,由於他們依賴於事件流。屬性的不斷變化與事件流相關。 數組
Bacon.js裏還有一個戲法。Bacon.fromPromise()函數是一種利用promise把事件搞成流的方式。 jQuery從1.5.0版本開始實現了promise接口,因此咱們所要作的僅僅是寫一個AJAX查詢函數, 在異步調用完成時觸發事件。每當promise被處理時,他就調用EventStream的訂閱者(subscribers)。 promise
var url = 'http://api.server.com/election-data?format=json'; var eventStream = Bacon.fromPromise(jQuery.ajax(url)); var subscriber = eventStream.onValue(function(data) { newRegions = getRegions(data); container.innerHTML = newRegions.map(function(r) { return r.render(); }).join(''); }
promise能夠想成是一個初始值;經過Bacon.js,咱們能夠對初始值惰性地等待。
如今咱們學完了響應式的內容,最後咱們能夠用些代碼來玩一玩它。
咱們能夠經過對純函數的鏈式調用改變訂閱者來作些事情,好比求和或者過濾不想要的結果, 咱們只需經過咱們所建立的對按鈕的onclick()處理函數來完成這些。
// 在函數外建立事件流 var eventStream = Bacon.onPromise(jQuery.ajax(url)); var subscribe = null; var url = 'http://api.server.com/election-data?format=json'; // 未被改變的訂閱者 $('button#showAll').click(function() { var subscriber = eventStream.onValue(function(data) { var newRegions = getRegions(data).map(function(r) { return new Region(r.name, r.percent, r.parties); }); container.innerHTML = newRegions.map(function(r) { return r.render(); }).join(''); }); }); // 顯示所有選舉狀況的按鈕 $('button#showTotal').click(function() { var subscriber = eventStream.onValue(function(data) { var emptyRegion = new Region('empty', 0, [{ name: 'Republican', votes: 0 }, { name: 'Democrat', votes: 0 }]); var totalRegions = getRegions(data).reduce(function(r1, r2) { newParties = r1.parties.map(function(x, i) { return { name: r1.parties[i].name, votes: r1.parties[i].votes + r2.parties[i].votes }; }); newRegion = new Region('Total', (r1.percent + r2.percent) / 2, newParties); return newRegion; }, emptyRegion); container.innerHTML = totalRegions.render(); }); }); // 只顯示報告大於50%的選區 $('button#showMostlyReported').click(function() { var subscriber = eventStream.onValue(function(data) { var newRegions = getRegions(data).map(function(r) { if (r.percent > 50) return r; else return null; }).filter(function(r) { return r != null; }); container.innerHTML = newRegions.map(function(r) { return r.render(); }).join(''); }); });
它的美麗之處在於:當用戶點擊按鈕時,事件流並無變化,可是訂閱者變化了,這就使全部的工做很平順。
JavaScript是一門美麗的語言。 它內在美確是因函數式編程而閃耀。它賦予了它良好的擴展性。事實上,打開了函數式的閘門使得頭等函數能夠作如此之多的事情。 概念互相堆疊,愈來愈高。
在這章,咱們深刻了函數式編程的範例,包括函數工廠、柯里化、函數組合等等。咱們用這些概念創建了很是模塊化的應用。 而後咱們展現瞭如何使用一些函數庫利用一樣的概念,函數組合,來控制執行順序。
這章覆蓋了幾個函數式編程的風格:數據泛型編程、基本上函數式的編程以及函數響應式編程。 他們之間並無很大不一樣,只是在不一樣場景應用的不一樣函數式編程模式而已。
上一章粗略說起了一些叫範疇論的東西,下一章咱們將學習關於它的更多內容以及如何使用它。
下一章 範疇輪