摘要:譯者團隊排名不分先后阿希冬青蘿卜萌萌輕量級函數式編程第章融會貫通現在你已經掌握了所有需要掌握的關于輕量級函數式編程的內容。回頭想想我們用到的函數式編程原則。這兩個函數組合成一個映射函數通過,這就是融合見第章。
原文地址:Functional-Light-JS
原文作者:Kyle Simpson-《You-Dont-Know-JS》作者
JavaScript 輕量級函數式編程 第 11 章:融會貫通關于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總結,是 JavaScript 中最嚴謹的邏輯。經過捶打磨練,成就了本書的中文版。本書包含了函數式編程之精髓,希望可以幫助大家在學習函數式編程的道路上走的更順暢。比心。
譯者團隊(排名不分先后):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、蘿卜、vavd317、vivaxy、萌萌、zhouyao
現在你已經掌握了所有需要掌握的關于 JavaScript 輕量級函數式編程的內容。下面不會再引入新的概念。
本章主要目標是概念的融會貫通。通過研究代碼片段,我們將本書中大部分主要概念聯系起來并學以致用。
建議進行大量深入的練習來熟悉這些技巧,因為理解本章內容對于將來你在實際編程場景中應用函數式編程原理至關重要。
準備我們來寫一個簡單的股票行情工具吧。
注意: 可以在本書的 GitHub 倉庫(https://github.com/getify/Functional-Light-JS)下的 ch11-code/ 目錄里找到參考代碼。同時,在書中討論到的函數式編程輔助函數的基礎上,我們篩選了所需的一部分放到了 ch11-code/fp-helpers.js 文件中。本章中,我們只會討論到其中相關的部分。
首先來編寫 HTML 部分,這樣便可以對信息進行展示了。我們在 ch11-code/index.html 文件中先寫一個空的 元素,在運行時,DOM 會被填充成:
我必須要事先提醒你的一點是,和 DOM 進行交互屬于輸入/輸出操作,這也意味著會產生一定的副作用。我們不能消除這些副作用,所以我們盡量減少和 DOM 相關的操作。這些技巧在第 5 章中已經提到了。
概括一下我們的小工具的功能:代碼將在每次收到添加新股票事件時添加 元素,并在股票價格更新事件發生時更新價格。
在第 11 章的示例代碼 ch11-code/mock-server.js 中,我們設置了一些定時器,把隨機生成的假股票數據推送到一個簡單的事件發送器中,來模擬從服務器收到的股票數據。我們暴露了一個 connectToServer() 接口來實現模擬,但是實際上,它只是返回了一個假的事件發送器。
注意: 這個文件是用來模擬數據的,所以我沒有花費太多的精力讓它完全符合函數式編程,不建議大家花太多時間研究這個文件中的代碼。如果你寫了一個真正的服務器 —— 對于那些雄心勃勃的讀者來說,這是一個有趣的加分練習 —— 這時你才應該考慮采用函數式編程思想來實現這些代碼。
我們在 ch11-code/stock-ticker-events.js 中,創建了一些 observable(通過 RxJS)連接到事件發送器對象上。通過調用 connectToServer() 來獲取這個事件的發射器,然后監聽名稱為 "stock" 的事件,通過這個事件來添加一個新的股票代碼,同時監聽名稱為 "stock-update" 的事件,通過這個事件來更新股票價格和漲跌幅。最后,我們定義一些轉換函數,來對這些 observable 傳入的數據進行格式化。
在 ch11-code/stock-ticker.js 中,我們將我們的界面操作(DOM 部分的副作用)定義在 stockTickerUI 對象的方法中。我們還定義了各種輔助函數,包括 getElemAttr(..),stripPrefix(..) 等等。最后,我們通過 subscribe(..) 監聽兩個 observable,來獲得格式化好的數據,渲染到 DOM 上。
股票信息一起看看 ch11-code/stock-ticker-events.js 中的代碼,我們先從一些基本的輔助函數開始:
function addStockName(stock) { return setProp( "name", stock, stock.id ); } function formatSign(val) { if (Number(val) > 0) { return `+${val}`; } return val; } function formatCurrency(val) { return `$${val}`; } function transformObservable(mapperFn,obsv){ return obsv.map( mapperFn ); }
這些純函數應該很容易理解。參見第 4 章 setProp(..) 在設置新屬性之前復制了對象。這實踐到了我們在第 6 章中學習到的原則:通過把變量當作不可變的變量來避免副作用,即使其本身是可變的。
addStockName(..) 用來在股票信息對象中添加一個 name 屬性,它的值和這個對象 id 一致。name 會作為股票的名稱展示在工具中。
有一個關于 transformObservable(..) 的頗為微妙的注意事項:表面上看起來在 map(..) 函數中返回一個新的 observable 是純函數操作,但是事實上,obsv 的內部狀態被改變了,這樣才能夠和 map(..) 返回的新的 observable 連接起來。這個副作用并不是個大問題,而且不會影響我們的代碼可讀性,但是隨時發現潛在的副作用是非常重要的,這樣就不會在出錯時倍感驚訝!
當從“服務器”獲取股票信息時,數據是這樣的:
{ id: "AAPL", price: 121.7, change: 0.01 }
在把 price 的值顯示到 DOM 上之前,需要用 formatCurrency(..) 函數格式化一下(比如變成 "$121.70"),同時需要用 formatChange(..) 函數格式化 change 的值(比如變成 "+0.01")。但是我們不希望修改消息對象中的 price 和 change,所以我們需要一個輔助函數來格式化這些數字,并且要求這個輔助函數返回一個新的消息對象,其中包含格式化好的 price 和 change:
function formatStockNumbers(stock) { var updateTuples = [ [ "price", formatPrice( stock.price ) ], [ "change", formatChange( stock.change ) ] ]; return reduce( function formatter(stock,[propName,val]){ return setProp( propName, stock, val ); } ) ( stock ) ( updateTuples ); }
我們創建了 updateTuples 元組來保存 price 和 change 的信息,包括屬性名稱和格式化好的值。把 stock 對象作為 initialValue,對元組進行 reduce(..)(參考第 8 章)。把元組中的信息解構成 propName 和 val,然后返回了 setProp(..) 調用的結果,這個結果是一個被復制了的新的對象,其中的屬性被修改過了。
下面我們再定義幾個輔助函數:
var formatDecimal = unboundMethod( "toFixed" )( 2 ); var formatPrice = pipe( formatDecimal, formatCurrency ); var formatChange = pipe( formatDecimal, formatSign ); var processNewStock = pipe( addStockName, formatStockNumbers );
formatDecimal(..) 函數接收一個數字作為參數(如 2.1)并且調用數字的 toFixed( 2 ) 方法。我們使用了第 8 章介紹的 unboundMethod(..) 來創建一個獨立的延遲綁定函數。
formatPrice(..),formatChange(..) 和 processNewStock(..) 都用到了 pipe(..) 來從左到右地組合運算(見第 4 章)。
為了能在事件發送器的基礎上創建 observable(見第 10 章),我們將封裝一個獨立的柯里化輔助函數(見第 3 章)來包裝 RxJS 的 Rx.Observable.fromEvent(..):
var makeObservableFromEvent = curry( Rx.Observable.fromEvent, 2 )( server );
這個函數特定地監聽了 server(事件發送器),在接受了事件名稱字符串參數后,就能生成 observable 了。我們準備好了創建 observer 的所有代碼片段后,用映射函數轉換 observer 來格式化獲取到的數據:
var observableMapperFns = [ processNewStock, formatStockNumbers ]; var [ newStocks, stockUpdates ] = pipe( map( makeObservableFromEvent ), curry( zip )( observableMapperFns ), map( spreadArgs( transformObservable ) ) ) ( [ "stock", "stock-update" ] );
我們創建了包含了事件名稱(["stock","stock-update"])的數組,然后 map(..)(見第 8 章)這個數組,生成了一個包含了兩個 observable 的數組,然后把這個數組和 observable 映射函數 zip(..)(見第 8 章)起來,產生一個 [ observable, mapperFn ] 這樣的元組數組。最后通過 spreadArgs(..)(見第 3 章)把每個元組數組展開為多帶帶的參數,map(..) 到了 transformObservable(..) 函數上。
得到的結果是一個包含了轉換好的 observable 的數組,通過數組結構賦值的方式分別賦值到了 newStocks 和 stockUpdates 兩個變量上。
到此為止,我們用輕量級函數式編程的方式來讓股票行情信息事件成為了 observable!在 ch11-code/stock-ticker.js 中我們會訂閱這兩個 observable。
回頭想想我們用到的函數式編程原則。這樣做有沒有意義呢?你能否明白我們是如何運用前幾章中介紹的各種概念的呢?你能不能想到別的方式來實現這些功能?
更重要的是,如果你用命令式編程的方法是如何實現上面的功能的呢?你認為兩種方式相比孰優孰劣?試試看用你熟悉的命令式編程的方式去寫這個功能。如果你和我一樣,那么命令式編程仍然會讓你感到更加自然。
在進行下面的學習之前,你需要明白的是,除了使你感到非常自然的命令式編程以外,你也已經能夠了解函數式編程的合理性了。想想看每個函數的輸入和輸出,你看到它們是怎樣組合在一起的了嗎?
在你豁然開朗以前一定要持續不斷地練習。
股票行情界面如果你熟悉了上一章節中的函數式編程模式,你就可以開始學習 ch11-code/stock-ticker.js 文件中的內容了。這里會涉及相當多的重要內容,所以我們將好好地理解整個文件中的每個方法。
我們先從定義一些操作 DOM 的輔助函數開始:
function isTextNode(node) { return node && node.nodeType == 3; } function getElemAttr(elem,prop) { return elem.getAttribute( prop ); } function setElemAttr(elem,prop,val) { // 副作用!! return elem.setAttribute( prop, val ); } function matchingStockId(id) { return function isStock(node){ return getStockId( node ) == id; }; } function isStockInfoChildElem(elem) { return /stock-/i.test( getClassName( elem ) ); } function appendDOMChild(parentNode,childNode) { // 副作用!! parentNode.appendChild( childNode ); return parentNode; } function setDOMContent(elem,html) { // 副作用!! elem.innerHTML = html; return elem; } var createElement = document.createElement.bind( document ); var getElemAttrByName = curry( reverseArgs( getElemAttr ), 2 ); var getStockId = getElemAttrByName( "data-stock-id" ); var getClassName = getElemAttrByName( "class" );
這些函數應該算是不言自明的。為了獲得 getElemAttrByName(..),我用了 curry(reverseArgs( .. ))(見第 3 章)而不是 partialRight(..),只是為了在這種特殊情況下,稍微提高一點性能。
注意,我標出了操作 DOM 元素時的副作用。因為不能簡單地用克隆的 DOM 對象去替換已有的,所以我們在不替換已有對象的基礎上,勉強接受了一些副作用的產生。至少如果在 DOM 渲染中產生一個錯誤,我們可以輕松地搜索這些代碼注釋來縮小可能的錯誤代碼。
matchingStockId(..) 用到了閉包(見第 2 章),它創建了一個內部函數(isStock(..)),使在其他作用域下運行時依然能夠保存 id 變量。
其他的輔助函數:
function stripPrefix(prefixRegex) { return function mapperFn(val) { return val.replace( prefixRegex, "" ); }; } function listify(listOrItem) { if (!Array.isArray( listOrItem )) { return [ listOrItem ]; } return listOrItem; }
定義一個用以獲取某個 DOM 元素的子節點的輔助函數:
var getDOMChildren = pipe( listify, flatMap( pipe( curry( prop )( "childNodes" ), Array.from ) ) );
首先,用 listify(..) 來保證我們得到的是一個數組(即使里面只有一個元素)。回憶一下在第 8 章中提到的 flatMap(..),這個函數把一個包含數組的數組扁平化,變成一個淺數組。
映射函數先把 DOM 元素映射成它的子元素數組,然后我們用 Array.from(..) 把這個數組變成一個真實的數組(而不是一個 NodeList)。這兩個函數組合成一個映射函數(通過 pipe(..)),這就是融合(見第 8 章)。
現在,我們用 getDOMChildren(..) 實用函數來定義股票行情工具中查找特定 DOM 元素的工具函數:
function getStockElem(tickerElem,stockId) { return pipe( getDOMChildren, filterOut( isTextNode ), filterIn( matchingStockId( stockId ) ) ) ( tickerElem ); } function getStockInfoChildElems(stockElem) { return pipe( getDOMChildren, filterOut( isTextNode ), filterIn( isStockInfoChildElem ) ) ( stockElem ); }
getStockElem(..) 接受 tickerElem DOM 節點作為參數,獲取其子元素,然后過濾,保證我們得到的是符合股票代碼的 DOM 元素。getStockInfoChildElems(..) 幾乎是一樣的,不同的是它從一個股票元素節點開始查找,還使用了不同的過濾函數。
兩個實用函數都會過濾掉文字節點(因為它們沒有其他的 DOM 節點那樣的方法),保證返回一個 DOM 元素數組,哪怕數組中只有一個元素。
主函數我們用 stockTickerUI 對象來保存三個修改界面的主要方法,如下:
var stockTickerUI = { updateStockElems(stockInfoChildElemList,data) { // .. }, updateStock(tickerElem,data) { // .. }, addStock(tickerElem,data) { // .. } };
我們先看看 updateStock(..),這是三個函數里面最簡單的:
var stockTickerUI = { // .. updateStock(tickerElem,data) { var getStockElemFromId = curry( getStockElem )( tickerElem ); var stockInfoChildElemList = pipe( getStockElemFromId, getStockInfoChildElems ) ( data.id ); return stockTickerUI.updateStockElems( stockInfoChildElemList, data ); }, // .. };
柯里化之前的輔助函數 getStockElem(..),傳給它 tickerElem,得到了 getStockElemFromId(..) 函數,這個函數接受 data.id 作為參數。把 元素(其實是數組形式的)傳入 getStockInfoChildElems(..),我們得到了三個 子元素,用來展示股票信息,我們把它們保存在 stockInfoChildElemList 變量中。然后把數組和股票信息 data 對象一起傳給 stockTickerUI.updateStockElems(..),來更新 中的數據。
現在我們來看看 stockTickerUI.updateStockElems(..):
var stockTickerUI = { updateStockElems(stockInfoChildElemList,data) { var getDataVal = curry( reverseArgs( prop ), 2 )( data ); var extractInfoChildElemVal = pipe( getClassName, stripPrefix( /stock-/i ), getDataVal ); var orderedDataVals = map( extractInfoChildElemVal )( stockInfoChildElemList ); var elemsValsTuples = filterOut( function updateValueMissing([infoChildElem,val]){ return val === undefined; } ) ( zip( stockInfoChildElemList, orderedDataVals ) ); // 副作用!! compose( each, spreadArgs ) ( setDOMContent ) ( elemsValsTuples ); }, // .. };
這部分有點難理解。我們一行行來看。
首先把 prop 函數的參數反轉,柯里化后,把 data 消息對象綁定上去,得到了 getDataVal(..) 函數,這個函數接收一個屬性名稱作為參數,返回 data 中的對應的屬性名稱的值。
接下來,我們看看 extractInfoChildElem:
var extractInfoChildElemVal = pipe( getClassName, stripPrefix( /stock-/i ), getDataVal );
這個函數接受一個 DOM 元素作為參數,拿到 class 屬性的值,然后把 "stock-" 前綴去掉,然后用這個屬性值("name","price" 或 "change"),通過 getDataVal(..) 函數,在 data 中找到對應的數據。你可能會問:“還有這種操作?”。
其實,這么做的目的是按照 stockInfoChildElemList 中的 元素的順序從 data 中拿到數據。我們對 stockInfoChildElemList 數組調用 extractInfoChildElem 映射函數,來拿到這些數據。
接下來,我們把 數組和數據數組壓縮起來,得到一個元組:
zip( stockInfoChildElemList, orderedDataVals )
這里有一點不太容易理解,我們定義的 observable 轉換函數中,新的股票行情數據 data 會包含一個 name 屬性,來對應 元素,但是在股票行情更新事件的數據中可能會找不到對應的 name 屬性。
一般來說,如果股票更新消息事件的數據對象不包含某個股票數據的話,我們就不應該更新這只股票對應的 DOM 元素。所以我們要用 filterOut(..) 剔除掉沒有值的元組(這里的值在元組的第二個元素)。
var elemsValsTuples = filterOut( function updateValueMissing([infoChildElem,val]){ return val === undefined; } ) ( zip( stockInfoChildElemList, orderedDataVals ) );
篩選后的結果是一個元組數組(如:[ , ".." ]),這個數組可以用來更新 DOM 了,我們把這個結果保存到 elemsValsTuples 變量中。
注意: 既然 updateValueMissing(..) 是聲明在函數內的,所以我們可以更方便地控制這個函數。與其使用 spreadArgs(..) 來把函數接收的一個數組形式的參數展開成兩個參數,我們可以直接用函數的參數解構聲明(function updateValueMissing([infoChildElem,val]){ ..),參見第 2 章。
最后,我們要更新 DOM 中的 元素:
// 副作用!! compose( each, spreadArgs )( setDOMContent ) ( elemsValsTuples );
我們用 each(..) 遍歷了 elemsValsTuples 數組(參考第 8 章中關于 forEach(..) 的討論)。
與其他地方使用 pipe(..) 來組合函數不同,這里使用 compose(..)(見第 4 章),先把 setDomContent(..) 傳到 spreadArgs(..) 中,再把執行的結果作為迭代函數傳到 each(..) 中。執行時,每個元組被展開為參數傳給了 setDOMContent(..) 函數,然后對應地更新 DOM 元素。
最后說明下 addStock(..)。我們先把整個函數寫出來,然后再一句句地解釋:
var stockTickerUI = { // .. addStock(tickerElem,data) { var [stockElem, ...infoChildElems] = map( createElement ) ( [ "li", "span", "span", "span" ] ); var attrValTuples = [ [ ["class","stock"], ["data-stock-id",data.id] ], [ ["class","stock-name"] ], [ ["class","stock-price"] ], [ ["class","stock-change"] ] ]; var elemsAttrsTuples = zip( [stockElem, ...infoChildElems], attrValTuples ); // 副作用!! each( function setElemAttrs([elem,attrValTupleList]){ each( spreadArgs( partial( setElemAttr, elem ) ) ) ( attrValTupleList ); } ) ( elemsAttrsTuples ); // 副作用!! stockTickerUI.updateStockElems( infoChildElems, data ); reduce( appendDOMChild )( stockElem )( infoChildElems ); tickerElem.appendChild( stockElem ); } };
這個操作界面的函數會根據新的股票信息生成一個空的 DOM 結構,然后調用 stockTickerUI.updateStockElems(..) 方法來更新其中的內容。
首先:
var [stockElem, ...infoChildElems] = map( createElement ) ( [ "li", "span", "span", "span" ] );
我們先創建 父元素和三個 子元素,把它們分別賦值給了 stockElem 和 infoChildElems 數組。
為了設置 DOM 元素的對應屬性,我們聲明了一個元組數組組成的數組。按照順序,每個元組數組對應上面四個 DOM 元素中的一個。每個元組數組中的元組由對應元素的屬性和值組成:
var attrValTuples = [ [ ["class","stock"], ["data-stock-id",data.id] ], [ ["class","stock-name"] ], [ ["class","stock-price"] ], [ ["class","stock-change"] ] ];
我們把四個 DOM 元素和 attrValTuples 數組 zip(..) 起來:
var elemsAttrsTuples = zip( [stockElem, ...infoChildElems], attrValTuples );
最后的結果會是:
[ [
如果我們用命令式的方式來把屬性和值設置到每個 DOM 元素上,我們會用嵌套的 for 循環。用函數式編程的方式的話也會是這樣,不過這時嵌套的是 each(..) 循環:
// 副作用!! each( function setElemAttrs([elem,attrValTupleList]){ each( spreadArgs( partial( setElemAttr, elem ) ) ) ( attrValTupleList ); } ) ( elemsAttrsTuples );
外層的 each(..) 循環了元組數組,其中每個數組的元素是一個 elem 和它對應的 attrValTupleList,這個元組數組被傳入了 setElemAttrs(..),在函數的參數中被解構成兩個值。
在外層循環內,元組數組的子數組(包含了屬性和值的數組)被傳遞到了內層的 each(..) 循環中。內層的迭代函數首先以 elem 作為第一個參數對 setElemAttr(..) 進行了部分實現,然后把剩下的函數參數展開,把每個屬性值元組作為參數傳遞進這個函數中。
到此為止,我們有了 元素數組,每個元素上都有了該有的屬性,但是還沒有 innerHTML 的內容。這里,我們要用 stockTickerUI.updateStockElems(..) 函數,把 data 設置到 上去,和股票信息更新事件的處理一樣。
然后,我們要把這些 元素添加到對應的父級 元素中去,我們用 reduce(..) 來做這件事(見第 8 章)。
reduce( appendDOMChild )( stockElem )( infoChildElems );
最后,用操作 DOM 元素的副作用方法把新的股票元素添加到小工具的 DOM 節點中去:
tickerElem.appendChild( stockElem );
呼!你跟上了嗎?我建議你在繼續下去之前,回到開頭,重新讀幾遍這部分內容,再練習幾遍。
訂閱 Observable最后一個重要任務是訂閱 ch11-code/stock-ticker-events.js 中定義的 observable,把事件傳遞給正確的主函數(addStock(..) 和 updateStock(..))。
注意,這兩個主函數接受 tickerElem 作為第一個參數。我們聲明一個數組(stockTickerUIMethodsWithDOMContext)保存了兩個中間函數(也叫作閉包,見第 2 章),這兩個中間函數是通過部分參數綁定的函數把小工具的 DOM 元素綁定到了兩個主函數上來生成的。
var ticker = document.getElementById( "stock-ticker" ); var stockTickerUIMethodsWithDOMContext = map( curry( reverseArgs( partial ), 2 )( ticker ) ) ( [ stockTickerUI.addStock, stockTickerUI.updateStock ] );
reverseArgs( partial ) 是之前提到的 partialRight(..) 的替代品,優化了性能。但是這里 partial(..) 是映射函數的目標函數。所以我們需要事先 curry(..) 化,這樣我們就可以先把第二個參數 ticker 傳給 partial(..),后面把主函數傳進去的時候就可以用到之前傳入的 ticker 了。數組中的這兩個中間函數就可以被用來訂閱 observable 了。
我們用閉包在這兩個中間函數中保存了 ticker 數據,在第 7 章中,我們知道了還可以把 ticker 保存在對象的屬性上,通過使用兩個函數上的指向 stockTickerUI 的 this 來訪問 ticker。因為 this 是個隱式的輸入(見第 2 章),所以一般來說不推薦用對象的方式,所以我使用了閉包的方式。
為了訂閱 observable,我們先寫一個輔助函數,提供一個未綁定的方法:
var subscribeToObservable = pipe( uncurry, spreadArgs )( unboundMethod( "subscribe" ) );
unboundMethod("subscribe") 已經柯里化了,所以我們用 uncurry(..)(見第 3 章)先反柯里化,然后再用 spreadArgs(..)(依然見第 3 章)來修改接受的參數的格式,所以這個函數接受一個元組作為參數,展開后傳遞下去。
現在,我們只要把 observable 數組和封裝好上下文的主函數 zip(..) 起來。生成一個元組數組,每個元組可以用之前定義的 subscribeToObservable(..) 輔助函數來訂閱 observable:
var stockTickerObservables = [ newStocks, stockUpdates ]; // 副作用!! each( subscribeToObservable ) ( zip( stockTickerUIMethodsWithDOMContext, stockTickerObservables ) );
由于我們修改了這些 observable 的狀態以訂閱它們,而且由于我們使用了 each(..) —— 總是和副作用相關! —— 我們用代碼注釋來說明這個問題。
就是這樣!花些時間研究比較這段代碼和它命令式的替代版本,正如我們之前在股票行情信息中討論到的一樣。真的,可以多花點時間。我知道這是一本很長的書,但是完整地讀下來會讓你能夠消化和理解這樣的代碼。
你現在打算在 JavaScript 中如何合理地使用函數式編程?繼續練習,就像我們在這里做的一樣!
總結我們在本章中討論的示例代碼應該被作為一個整體來閱讀,而不僅僅是作為章節中所展示的支離破碎的代碼片段。如果你還沒有完整地閱讀過,現在請停下來,去完整地閱讀一遍代碼目錄下的文件吧。確保你在完整的上下文中了解它們。
示例代碼并不是實際編寫代碼的范例,只是提供了一種描述性的,教授如何用輕量級函數式的技巧來解決此類問題的方法。這些代碼盡可能多地把本書中不同概念聯系起來。這里提供了比代碼片段更真實的例子來學習函數式編程。
我相信,隨著我不斷地學習函數式編程,我會繼續改進這個示例代碼。你現在看到的只是我在學習曲線上的一個快照。我希望對你來說也是如此。
在我們結束本書的主要內容時,我們一起回顧一下我在第 1 章中提到的可讀性曲線:
在學習函數式編程的過程中,理解這張圖的真諦,并且為自己設定合理的預期,是非常重要的。你已經到這里了,這已經是一個很大的成果了。
但是,當你在絕望和沮喪的低谷時,別停下來。前面等待你的是一種更好的思維方式,可以寫出可讀性更好,更容易理解,更容易驗證,最終更加可靠的代碼。
我不需要再為開發者們不斷前行想出更多崇高的理由。感謝你參與到我學習 JavaScript 中的函數式編程的原理的過程中來。我希望你的學習過程和我的一樣,充實而充滿希望!
【上一章】翻譯連載 | 第 10 章:異步的函數式(下)-《JavaScript輕量級函數式編程》 |《你不知道的JS》姊妹篇
iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、當當開售。
iKcamp官網:https://www.ikcamp.com
訪問官網更快閱讀全部免費分享課程:
《iKcamp出品|全網最新|微信小程序|基于最新版1.0開發者工具之初中級培訓教程分享》
《iKcamp出品|基于Koa2搭建Node.js實戰項目教程》
包含:文章、視頻、源代碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89838.html
摘要:我稱之為輕量級函數式編程。序眾所周知,我是一個函數式編程迷。函數式編程有很多種定義。本書是你開啟函數式編程旅途的絕佳起點。事實上,已經有很多從頭到尾正確的方式介紹函數式編程的書了。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團隊(排名不分先后):阿希、blueken、brucecham、...
摘要:本書主要探索函數式編程的核心思想。我們在中應用的僅僅是一套基本的函數式編程概念的子集。我稱之為輕量級函數式編程。通常來說,關于函數式編程的書籍都熱衷于拓展閱讀者的知識面,并企圖覆蓋更多的知識點。,本書統稱為函數式編程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團隊(排名不分先后)...
摘要:一旦我們滿足了基本條件值為,我們將不再調用遞歸函數,只是有效地執行了。遞歸深諳函數式編程之精髓,最被廣泛引證的原因是,在調用棧中,遞歸把大部分顯式狀態跟蹤換為了隱式狀態。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;...
摘要:所以我覺得函數式編程領域更像學者的領域。函數式編程的原則是完善的,經過了深入的研究和審查,并且可以被驗證。函數式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數式編程編程者會認為形式主義本身有助于學習。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液...
摘要:如果你的運行緩慢,你可以考慮是否能優化請求,減少對的操作,盡量少的操,或者犧牲其它的來換取性能。在認識描述這些核心元素的過程中,我們也會分享一些當我們構建的時候遵守的一些經驗規則,一個應用應該保持健壯和高性能來維持競爭力。 一個開源的前端錯誤收集工具 frontend-tracker,你值得收藏~ 蒲公英團隊最近開發了一款前端錯誤收集工具,名叫 frontend-tracker ,這款...
閱讀 3620·2021-09-30 09:59
閱讀 2229·2021-09-13 10:34
閱讀 576·2019-08-30 12:58
閱讀 1507·2019-08-29 18:42
閱讀 2198·2019-08-26 13:44
閱讀 2921·2019-08-23 18:12
閱讀 3320·2019-08-23 15:10
閱讀 1624·2019-08-23 14:37