JavaScript 函數組合:構建更易維護的代碼
JavaScript 函數組合是一種將多個簡單函數組合成單個更複雜函數的技術,這些函數按邏輯順序對給定數據執行子函數操作。函數應用的順序會產生不同的結果。
為了編寫可維護的代碼,理解每個被組合函數的返回類型至關重要,以確保下一個函數可以正確處理它。 JavaScript 不會阻止組合返回不合適類型函數,因此這部分責任落在程序員身上。
對不熟悉函數式編程範式的人來說,組合函數可能會使代碼看起來很複雜。但是,隨著 ES2015 語法的出現,使用箭頭函數,可以一行代碼創建簡單的組合函數,而無需特殊的組合方法。
組合函數應該是純函數,這意味著每次將特定值傳遞給函數時,函數都應返回相同的結果,並且函數不應產生改變自身外部值的副作用。這將產生更簡潔、更易讀的代碼。
本文由 Jeff Mott、Dan Prince 和 Sebastian Seitz 共同評審。感謝所有 SitePoint 的同行評審者,使 SitePoint 的內容達到最佳狀態!
函數式思維的優勢之一是能夠使用小型、易於理解的單個函數來構建複雜的功能。 但有時這需要反向思考問題,而不是正向思考,以便找出如何創建最優雅的解決方案。在本文中,我將採用循序漸進的方法來檢查 JavaScript 中的函數組合,並演示它如何產生更易於理解且錯誤更少的代碼。
嵌套函數
組合是一種技術,它允許你將兩個或多個簡單函數組合成一個更複雜的函數,該函數按邏輯順序對傳入的任何數據執行每個子函數。要獲得此結果,你需要將一個函數嵌套在另一個函數中,並對內部函數的結果重複執行外部函數的操作,直到產生結果。並且結果可能因函數應用的順序而異。這可以使用我們已經在 JavaScript 中熟悉的編程技術輕鬆演示,方法是將函數調用作為參數傳遞給另一個函數:
<code class="language-javascript">function addOne(x) { return x + 1; } function timesTwo(x) { return x * 2; } console.log(addOne(timesTwo(3))); //7 console.log(timesTwo(addOne(3))); //8</code>
在這種情況下,我們定義了一個 addOne() 函數來為一個值加 1,以及一個 timesTwo() 函數將一個值乘以 2。通過將一個函數的結果作為另一個函數的參數傳入,我們可以看到如何將其中一個嵌套在另一個中會產生不同的結果,即使初始值相同也是如此。內部函數先執行,然後結果傳遞給外部函數。
命令式組合
如果你想重複執行相同的操作序列,定義一個新函數來自動應用第一個函數和第二個函數可能會很方便。這可能看起來像這樣:
<code class="language-javascript">function addOne(x) { return x + 1; } function timesTwo(x) { return x * 2; } console.log(addOne(timesTwo(3))); //7 console.log(timesTwo(addOne(3))); //8</code>
在這種情況下,我們手動將這兩個函數按特定順序組合在一起。我們創建了一個新函數,該函數首先將傳遞的值分配給 holder 變量,然後通過執行第一個函數,然後是第二個函數來更新該變量的值,最後返回該 holder 的值。 (請注意,我們使用名為holder 的變量來臨時保存我們傳入的值。對於這樣一個簡單的函數,額外的局部變量似乎是多餘的,但即使在命令式JavaScript 中,將傳入函數的參數值視為常量也是一個好習慣。局部修改它們是可能的,但這會造成關於在函數的不同階段調用時參數值是什麼的混淆。)類似地,如果我們想創建一個另一個新函數以相反的順序應用這兩個較小的函數,我們可以執行以下操作:
<code class="language-javascript">// ...来自上面的先前函数定义 function addOneTimesTwo(x) { var holder = x; holder = addOne(holder); holder = timesTwo(holder); return holder; } console.log(addOneTimesTwo(3)); //8 console.log(addOneTimesTwo(4)); //10</code>
當然,這段代碼開始看起來非常重複。我們的兩個新的組合函數幾乎完全相同,只是它們調用的兩個較小函數的執行順序不同。我們需要簡化它(就像不要重複自己一樣)。此外,使用像這樣改變其值的臨時變量並不是很函數式,即使它隱藏在我們正在創建的組合函數內部也是如此。底線:我們可以做得更好。
創建函數式組合
讓我們創建一個組合函數,它可以採用現有的函數並將它們按我們想要的順序組合在一起。為了以一致的方式做到這一點,而無需每次都處理內部細節,我們必須決定要將函數作為參數傳入的順序。我們有兩個選擇。參數將分別是函數,它們可以從左到右或從右到左執行。也就是說,使用我們提出的新函數,compose(timesTwo, addOne) 可以表示 timesTwo(addOne()) 從右到左讀取參數,或者 addOne(timesTwo()) 從左到右讀取參數。從左到右執行參數的優點是它們將以英語相同的閱讀方式讀取,這與我們命名組合函數 timesTwoAddOne() 以暗示乘法應在加法之前發生的方式非常相似。我們都知道邏輯命名對於簡潔易讀的代碼的重要性。從左到右執行參數的缺點是待操作的值必須放在前面。但是,將值放在前面使得將來用其他函數組合結果函數不太方便。為了很好地解釋這種邏輯背後的思想,你無法超越 Brian Lonsdorf 的經典視頻 Hey Underscore, You’re Doing it Wrong。 (儘管應該注意的是,現在Underscore 有一個fp 選項可以幫助解決Brian 在將Underscore 與函數式編程庫(例如lodash-fp 或Ramda)一起使用時討論的函數式編程問題。)無論如何,我們真正想要做的是首先傳入所有配置數據,然後最後傳入要操作的值。因此,將我們的組合函數定義為讀取其參數並從右到左應用它們最合乎邏輯。因此,我們可以創建一個看起來像這樣的基本組合函數:
<code class="language-javascript">function addOne(x) { return x + 1; } function timesTwo(x) { return x * 2; } console.log(addOne(timesTwo(3))); //7 console.log(timesTwo(addOne(3))); //8</code>
使用這個非常簡單的組合函數,我們可以更簡單地構建我們之前的兩個複雜函數,並看到結果相同:
<code class="language-javascript">// ...来自上面的先前函数定义 function addOneTimesTwo(x) { var holder = x; holder = addOne(holder); holder = timesTwo(holder); return holder; } console.log(addOneTimesTwo(3)); //8 console.log(addOneTimesTwo(4)); //10</code>
雖然這個簡單的組合函數有效,但它並沒有考慮許多限制其靈活性和適用性的問題。例如,我們可能想要組合兩個以上的函數。此外,我們在此過程中會丟失這些信息。我們可以解決這些問題,但為了掌握組合的工作原理,這不是必需的。與其自己編寫,不如從現有的函數式庫(例如 Ramda)繼承更強大的組合,默認情況下,它確實考慮了參數從右到左的順序。
類型是你的責任
重要的是要記住,程序員有責任知道每個被組合函數的返回類型,以便下一個函數可以正確處理它。與執行嚴格類型檢查的純函數式編程語言不同,JavaScript 不會阻止你嘗試組合返回不合適類型值的函數。你不僅限於傳遞數字,甚至不限於從一個函數到下一個函數保持相同的變量類型。但是你負責確保你正在組合的函數已準備好處理前一個函數返回的任何值。
考慮你的受眾
始終記住,其他人將來可能需要使用或修改你的代碼。在傳統的 JavaScript 代碼中使用組合對於不熟悉函數式編程範式的人來說可能會顯得複雜。目標是更簡潔、更易於閱讀和維護的代碼。但是,隨著 ES2015 語法的出現,甚至可以使用箭頭函數將簡單的組合函數作為單行調用來創建,而無需特殊的組合方法:
<code class="language-javascript">function addOne(x) { return x + 1; } function timesTwo(x) { return x * 2; } console.log(addOne(timesTwo(3))); //7 console.log(timesTwo(addOne(3))); //8</code>
今天就開始組合吧
與所有函數式編程技術一樣,重要的是要記住你的組合函數應該是純函數。簡而言之,這意味著每次將特定值傳遞給函數時,函數都應返回相同的結果,並且函數不應產生改變自身外部值的副作用。當您有一組想要應用於數據的相關功能,並且您可以將該功能的組件分解為可重用且易於組合的函數時,組合嵌套非常方便。與所有函數式編程技術一樣,我建議謹慎地將組合添加到你的現有代碼中以熟悉它。如果你做對了,結果將是更簡潔、更乾燥、更易讀的代碼。難道這不是我們都想要的嗎?
(以下為FAQ,已根據原文進行調整和精簡,並避免重複信息)
關於函數組合和可維護代碼的常見問題
什麼是編程中的函數組合? 函數組合是將簡單函數組合成更複雜函數的概念。你可以像搭積木一樣,用這些小型可重用的函數創建複雜、強大的功能。這有助於使代碼更易讀、更易維護和更具可擴展性。
函數組合如何促進可維護的代碼? 函數組合通過鼓勵使用小型、可重用的函數來促進可維護性。這些函數更容易理解、測試和調試。當這些小型函數組合在一起以創建更複雜的功能時,更容易查明和修復可能出現的任何問題。這種模塊化方法還可以更容易地更新或修改代碼的某些部分,而不會影響整個系統。
編寫可維護代碼的最佳實踐是什麼? 編寫可維護的代碼涉及多項最佳實踐,包括:保持函數小巧且專注於單一任務;使用有意義的變量和函數名稱;對代碼進行註釋以解釋代碼邏輯背後的“原因”;遵循一致的編碼風格和結構。
函數組合與函數式編程有何關係? 函數組合是函數式編程中的一個基本概念。函數式編程是一種將計算視為數學函數的求值並避免更改狀態和可變數據的範例。在這種範例中,函數組合在從更簡單的函數構建更複雜的函數中起著至關重要的作用,從而提高了代碼的可重用性和可維護性。
實現函數組合的挑戰是什麼? 函數組合雖然有很多好處,但也可能帶來挑戰。一個挑戰是,如果管理不當,它可能會導致難以閱讀的代碼,尤其是在組合多個函數時。在組合函數鏈中處理錯誤和異常也可能很困難。但是,可以通過良好的編碼實踐和正確使用函數組合來減輕這些挑戰。
函數組合如何改進代碼測試? 函數組合可以使代碼測試更輕鬆、更高效。由於組合函數是由較小、獨立的函數組成的,因此你可以單獨測試每個小型函數。這使得更容易隔離和修復錯誤。它還促進了單元測試的實踐,其中軟件的每個部分都單獨進行測試以確保其正常工作。
以上是功能組成:可維護代碼的構建塊的詳細內容。更多資訊請關注PHP中文網其他相關文章!