如何編寫高質量的函數 -- 打通任督二脈篇[理論卷]

3月的春風

凡是點進來的老鐵都會受到 dva 的賣萌祝福。我會理論結合實踐的去闡述:如何運用函數式編程思想去編寫高質量的函數。 在這篇文章中,你能夠收穫一個滿意的答案。html

有時候,世間的答案並不重要,重要的是,你如何去看待和相信這份答案。前端

理論卷看完的小夥伴能夠點擊下面連接,開啓實戰卷:java

如何編寫高質量的函數 -- 打通任督二脈篇[實戰卷]react

這部分是寫到1萬字後加的

嗯,原本打算一篇搞定,但是寫着寫着就到了 10000 字了。雖然已經較簡潔了,可是涉及到的知識有點多,仍是要花字數去闡述清楚的。因而乎,我決定分爲上下兩篇來完成打通 任督二脈篇 這個篇章。git

如何寫這篇文章

這是我編寫高質量函數系列的第三篇文章 -- 打通任督二脈。github

前兩篇文章分別爲:算法

如何編寫高質量的函數 -- 敲山震虎篇編程

如何編寫高質量的函數 -- 命名/註釋/魯棒篇後端

第三篇是關於 如何運用函數式編程來編寫高質量函數 的一篇文章。api

關於函數式編程,我研究了很長時間,這裏我想結合如何編寫高質量函數,來寫出和其餘函數式編程文章不同的 feel

本着不重複造輪子,只造更好的輪子的原則,在寫這篇文章以前,我思考了一下子,我發現本身面臨兩個問題,這裏我簡單說一下,問題和答案以下:

我寫文章時面臨的問題

第一個問題:如何高質量的闡述函數式編程

第二個問題:如何真正意義上的經過打通函數式的任督二脈來提升編寫函數的質量

個人答案

第一個問題的答案

我會經過闡述幾個很細節但很是重要的函數式編程知識,經過這些細節來介紹函數式編程,同時向你們展現函數式編程的知識體系,以及和咱們如今大部分的知識體系有什麼共同點和不一樣點。

第二個問題的答案

這裏我會經過分析開源項目的代碼和平時工做中的代碼以及小夥伴的代碼,來展現如何運用函數式編程來提升函數的質量。

上述是我面臨的一些問題和解決方案,最後寫出了這兩篇文章,但願可以得到你們的喜歡吧。(固然嘍,拉勾打個賭,敢不敢不看完文章就先給我點個贊,嘿嘿嘿)

番外篇——當代前端工程師面臨的考驗

忽然的番外,讓我措手不及,來不及思考,就要進入新世界了。

需求的複雜度愈來愈高

我想說的是:因爲前端需求的複雜度愈來愈高,大量的業務邏輯放到前端去實現,這雖然減輕了後端的壓力,可是卻讓前端的壓力大大增長,前端工程師爲此要完成存儲、異步、事件等一系列綜合性的操做。

需求完成度愈來愈苛刻

咱們能夠看一個公式:

f(需求) = 完美

簡單理解就是:輸入需求,而後輸出儘量完美的結果。

在需求複雜度持續增長的條件下,需求完成度卻愈來愈苛刻,這樣的狀況,會致使前端工程師的壓力很大。咱們不只要解決各類負責的業務場景,還要保證還原度和開發效率,以及線上運行的性能等方面的表現。

前端工程師的廣泛目標

我總結了一下,大體有四點:

  1. 加強開發技能
  2. 提升代碼質量
  3. 提升開發效率
  4. 享受生活和 coding 的樂趣

這四點總結起來,咱們要達成的目的,其實就是下面這張圖:

我能作的就是提早告訴你 One Piece 的內容。嗯,沒錯,就是上面這張圖。加油吧,各位兄得。

前端工程師的廣泛現狀

但現實老是美好和殘酷混合的,因爲各類緣由,前端工程師的廣泛現狀能夠用下面幾點概況。

  1. coderoop 思想不強,致使代碼質量不高
  2. coderfp 思想不強,致使代碼質量不高
  3. JS 語言自身的問題,致使代碼的可控性較差

爲何我會這樣說呢,且聽我娓娓道來

首先,JS 是一門很靈活的編程語言,在靈活和基礎不牢的雙重做用下,代碼就會積累足夠的複雜度,代碼的質量和可控性就會出現問題。

關於 oopfp 的思想,能夠用下面兩句話進行總結概況

面對對象編程 oop 經過封裝變化,使得代碼質量更高。

函數式編程 fp 經過最小化變化,使得代碼質量更高。

而前端工程師的 oopfp 思想都不是很成熟,雖然如今前端界一直在推進吸取這些思想,但目前確實仍是一個問題。

我我的的見解和總結

對於上面我寫的那些話,我不是說前端工程師底子差什麼的,我是在客觀闡述一些觀點,你們能夠想一下,爲何如今不去說 PHP 了,還不是由於愛你( 前端工程師 )啊。

咱們就像是新世代被選中的一羣孩子,能作的就是接受這份挑戰。就算之後某個時刻,你不作 coder 了 ,可是如今,身爲前端 coder 的你,應該去打敗它,不爲別的,只爲做爲 coder 心中的那一份執着和榮耀。

科普篇--關於編程語言

你跟我來,我從番外篇穿梭到科普篇的世界裏。

JavaScript 是函數式編程語言嗎

要回答這個問題,首先,要明確的一點是:

JS 不是純正的函數式編程語言,因此它不是函數式編程語言。

爲何呢?

由於它具備 for 循環等命令式味道太重的語言設計。真正的純函數式編程語言,好比 Haskell 是沒有 for 循環的,遍歷操做都是使用遞歸。

爲何我要提這個點,是由於我想說一個道理,那就是:

既然 JavaScript 不是純正的函數式編程語言,那咱們就沒有必要在使用 JS 的時候,所有采用 FP 思想,咱們要作的,應當是取長補短。其實這句話也從另外一個方面結束了以前掘金上關於函數式編程的討論。

那 JavaScript 是什麼語言?

這裏我引用一句話:

天然語言是沒有主導範式的,JavaScript 也一樣沒有。開發者能夠從過程式、函數式和麪向對象的大雜燴中選擇本身須要的,再適時的地把它們融爲一體。

出自:Angus Croll, 《If Hemingway Wrote JavaScript》

聽我分析,雖然上面的話可能過於絕對,若是想知道上面的話爲何會這樣說,我建議你們能夠去學一下編譯原理知識。

爲何我要這樣建議

是由於我我的認爲,全部的高級語言,都是要經過翻譯來變成機器語言的,而越底層的實現,其思想和方法越具體和單一。好比,詞法分析,語法分析,業界的方法屈指可數。也是由於只有統一,才能創建標準,就像當今的 TCP/IP 協議。

固然,我胡謅那麼多沒有啥卵用,但咱們依然能夠知道並肯定一件事情,那就是:

討論 JavaScript 是一門基於什麼樣的語言,是沒有任何意義的。

一等公民到底是什麼鬼哦

都是 JS 的函數是一等公民,那一等公民到底是什麼鬼哦?其實,編程語言也是分 階級 的,大體有以下階級:

一等公民

若是編程語言中的一個值,能夠做爲參數傳遞,能夠從程序中返回,能夠賦值給變量,就稱它爲一等公民。

二等公民

能夠做爲參數傳遞,可是不能從子程序中返回,也不能賦給變量。

三等公民 它的值連做爲參數傳遞都不行。

因爲 JS 的函數是一等公民,因此咱們能夠把函數當初參數,也能夠做爲返回值,也能夠賦值給變量。也正由於是一等公民,JS 的函數式編程才能發光發熱。

賣萌篇--函數的分類

嗯,是否是很氣,仁兄別急,dva 如今拉着你的手,從科普片場來到了賣萌片場。

下面是我心血來潮,總結加胡謅的一些見解。

從純潔性角度分類

  1. 純函數
  2. 非純函數

嗯,我這分類沒誰了,就像人,能夠分爲是人和不是人...

從調用形式角度分類

中綴函數

代碼以下:

6 * 6 
複製代碼

6 * 6 中的 * 就是一個函數。並且因爲在兩個表達式的中間,因此叫中綴。看到這,你們應該有這樣的思惟,那就是在 JS 中用這些符號的時候,要天然的將其想象成函數。這樣你會發現代碼會變得更容易理解。

前綴函數

代碼以下:

function f(str) {
  console.log(str)
}

f('hello, i am 源碼終結者')
複製代碼

這種在函數名 f 後面加上參數的執行形式,就稱函數 f 爲前綴函數。這個我就不說了,你們應該很熟悉了。

提一下 let

當我學習 haskell 的時候,我發現 haskell 也有 let 。它是這樣介紹的:

let 後加上表達式,能夠容許你在任何位置定義局部變量。

當看到這,我馬上想起來 JS 中 的 let 。這和 JSlet 不謀而合,雖然其餘語言也有 let ,好比 go 語言。但我如今更有信心肯定, JSlet 的誕生,借鑑的應該是 haskell 中的 let

因此,在 JS 中,我更想將 let 稱爲綁定。haskell 中介紹了 let 的缺點,這裏咱們推一下能夠知道,JS 中的 let 缺點也很明顯,那就是 let 綁定的做用域被限制的很小。

因此你會發現,在某些場景下,使用 var 也是很是合理和正確的。

我是分割線,熱身要結束了,即將開始正題。


文章總體思路

當失去之後,再次擁有時,纔會倍加珍惜吧。

嗯,那開始正題吧。

首先能夠知道的一點是:本文的思路是清晰的,文章前半段,我會着重闡述函數式編程的理論知識。好比我會闡述不少人心中對於函數式編程的一些困惑。文章後半段,我會結合開源項目的源碼和實際工做中的 coding ,來展現如何運用函數式編程來編寫高質量的函數。

下文的 函數式編程 統稱爲 FP

關於 FP 你真正困惑的那些點--FP理論知識

關於理論知識,我不會把 FP 的方方面面都涵蓋,我會嘗試從小的點出發,帶你去認識 FP ,讓你們能夠在宏觀層面上對函數式編程有一個較清晰的認知。

下面,我會從如下這些點出發:

  • 函數式編程存在的意義
  • 聲明式編程
  • 數學中的函數
  • 純函數/純潔性/等冪性/引用透明/
  • 反作用
  • 數據的不可變性
  • 惰性
  • 態射
  • 高階性
  • 其餘理論知識

PS: 這裏我不會對單個點進行詳細介紹,只說出我我的對其的一個認知和理解。

函數式編程存在的意義

這是一本書中的解釋:

函數式編程的目的是使用函數來抽象做用在數據之上的控制流與操做,從而在系統中消除反作用並減小對狀態的改變。

這句話總結的很不錯,看起來好像很深奧,小夥伴們不要怕,看下面個人分析你就明白了。

咱們看一下上面的解釋,會發現這句話,實際上是在說:

函數式編程是一種解決問題的方式。

爲何我會這樣說呢,繼續往下看:

咱們繼續分析上面的解釋,能夠知道下面兩點:

第一點:FP 抽象了過程,好比控制流(如 for 循環)

第二點:FP 消除程序中的反作用(後面說)

稍加推導能夠知道第三點:

FP 的存在必定是解決了其餘範式的一些缺陷,好比面對對象範式的一些缺陷,如反作用在面對對象中很常見,從 Java 的鎖機制能夠看出反作用的表現,固然我對鎖機制這種觀點可能不許備,可是關於必定是解決了其餘範式的一些缺陷這個觀點,仍是很正確的,否則就沒有存在的意義了。

提一下函數式編程和抽象的關係

上面第一點提到,FP 抽象了過程,關於抽象,咱們知道,抽象層次越低,代碼重用的難度就會越大,出現錯 誤的可能性就會越大。

爲何說抽象層次越低,代碼重用的難度就會越呢?

我來舉個栗子,你就會明白了。就一句話:每次吃麪條時,做爲 coder 的你,有兩種選擇:

第一種:直接去超市買把麪條,下着吃。

第二種:本身和麪,本身擀麪條,而後下着吃。

上面兩種方法,你會選哪種呢,其實我已經猜到了,抽象的高和低就是第一種和第二種的區別,小夥伴好好想想。

綜上,咱們能夠知道:

FP 的本質意義就在於此,真正理解了其存在的目的,也就理解了咱們爲何要使用函數式編程了。

前段日子掘金上關於 FP 的討論

以前掘金上有那麼幾天很是熱鬧,是關於函數式編程的討論,有說函數式好用的,有說不用函數式的政治正確的,有 /^駁/ 的。那些天,天天刷掘金,都能看到相關的文章,老是想說些什麼,可是最後也沒有說什麼。

可是,若是真正理解了函數式編程的意義,也就不會有這麼多不一樣的聲音了。

但聲音這麼多,主要緣由應該仍是:

寫文章的時候,偶爾會說出很絕對的話,這會致使其餘做者進行迴應,緊接着,事件持續發酵,激起了可愛的吃瓜羣衆的興趣,遂開啓了大規模的圍觀和討論。

在這裏,我想用一句話來結束爭議,那就是:

函數式不是工具,也不是 api ,它是一種解決問題的方式。

這句話,不作解釋了,自行腦補吧。

聲明式編程

說到聲明式,就要提到命令式。聲明式和命令式,是目前前端所面臨的兩種編程範式。

小夥伴應該知道,函數式編程是聲明式編程。當你看完上面我對 FP 意義的解釋後,你就會明白爲何會有聲明式編程存在了。

FP 能夠將控制流抽象掉,不要其暴露在程序中,這就是一種聲明式編程。

關於編程範式,你們能夠快速瀏覽下面關於編程範式的維基:

[維基百科連接-編程範式]zh.wikipedia.org/wiki/編程範型

思考題:聲明式編程的軟肋在哪,它與命令式編程的本質區別是什麼?小夥伴能夠思考一下。

數學中的函數

爲何要說這個,我認爲,若是想更好的理解和運用函數式編程,就必需要去了解數學中的函數,由於函數式編程的不少概念和數學中函數的思想是強相關的。

這裏我不作分析了,我貼一個維基連接:

函數-維基

小夥伴們自行學習一下,看的時候,你們能夠順帶滑到最下面,看看有相關範疇論的介紹,範疇學的思想對函數式編程很是重要。

純函數/純潔性/等冪性/引用透明性

這幾個詞在 FP 中很常見,在我看來,這些詞最終的目標都是同樣的,那就是達成上文提到的 FP 的目的。這裏我用簡潔的語言去闡述一下這些詞背後的理念。

這些詞表明的意識實際上是同樣的,能夠這樣理解:

純函數的特性包括純潔性,等冪性,引用透明性。

那純函數是什麼呢?能夠經過下面的說法來認識純函數。

全部的函數對於相同的輸入都將返回相同的輸出。

PS: 固然我這篇文章並非讓你們運用純函數去實現編程,而是想讓你們運用函數式的思想去實現編程,不必定要是純函數的,引純函數知識 FP 的一個思想而已。

函數的反作用

上面說了純函數,那麼如何理解函數的反作用呢,能夠這樣理解:

相同的輸入,必定會輸出相同的輸出,若是沒有的話,那就說明函數是有反作用的。

從這句話,咱們認真想一下,能夠推導出:

由於 I/O 的輸出是具備不肯定性的,因此一切的 I/O 都是有反作用的。

如何處理反作用

後面實戰再說,這裏我提一點,你們能夠去看看 dva 的文檔或者源碼,其中關於異步請求的,都只在 effect 中完成,而 effect 在函數式編程中有反作用的意識,這是一種將函數的反作用隔離起來的設計思想。

數據的不可變性

關於數據的不可變性,仍是很重要的,咱們須要去深刻的理解它。我想幾個問題:

第一:前端出現數據的不可變性的目的

第二:前端如何作到數據的不可變性

下面咱們來一次回答這兩個問題。

前端出現數據的不可變性的目的

回答這個問題以前,我想說另一個事情,那就是:許多編程語言都支持讓數據不可變,好比 javafinal ,能夠看看這篇文章,很不錯。看完,你會對數據不可變有一個交叉的瞭解。

淺談Java中的final關鍵字

ok ,我我的認爲,不可變性思想很簡單,但很重要,我舉個例子你就明白了。

代碼以下:

const obj = {}
const arr = []
const sum = 0

function main(arg){
  // TODO:
}

main(obj)
main(arr)
main(sum)
複製代碼

從上面的代碼,咱們能夠知道如下幾點:

第一點:main 函數的參數都是依賴了外部做用域的變量

第二點:變量 obj arr sum 是能夠被外界改變的

第三點:要注意一個事情,咱們目前沒法避免外界對這些變量進行改變,有多是必需要改變的,也有多是無心間被改變了。

第四點:雖然咱們沒法避免外界對其可能形成的影響,但咱們能夠避免咱們本身對其可能形成的影響,好比咱們使用 main 直接對 obj arr 自己進行操做,這樣的話就會使得 objarr 被改變,若是改變了,那在咱們屢次調用 main 函數時,就可能會出現問題。

第五點:上面的代碼已經違反了函數式編程的原則,就是不能依賴外界的數據,這樣會有反作用。

基於上面五點後的答案

知道上面五點的信息後,咱們想一下,若是能把arr obj sum 設置成不可變數據結構。那咱們就能夠直接避免了外界和咱們本人對其形成的全部影響。由於設置了數據不可變性,外界無法去改變它們。因此這就是爲何,前端會出現數據不可變性這一說,由於當前端愈來愈複雜的時候,這種要求就表現的更加明顯了。可是,很遺憾的時候,JS 是不支持不可變的數據結構的。

前端如何作到數據的不可變性

前端如何作到數據的不可變性,這裏有三種方法:

第一種方法:

在函數內對引用類型建立一個新的副本,而後對副本進行指定業務處理。

第二種方法:

對引用類型進行封裝,業界經常使用的方法就是值對象模式,經過閉包的形式暴露出去。

第三種方法:

經過 JSapi 來實現,用規則來阻止對象狀態的修改。好比使用 Object.freeze() 來凍結一個對象,當外界試圖對這個對象進行增刪改屬性的時候,就會致使程序錯誤。對於第三個方法,咱們看一下 react 的源碼中,哪些地方用到了,如圖所示:

ReactDOMComponent.js 中對 nextProp 對象進行了凍結,可能不少人不明白爲何要凍結,其實很好理解,咱們從因果關係來理解。

首先 react 假設 nextProp 不會發生改變,那麼怎麼保證不會發生改變呢?確定是經過 Object.freeze(nextProp)nextProp 進行凍結,凍結後,react 就能夠知道,從這個時刻開始,nextProp 對象就是不可變的了。因此它就能夠進行下一步,按照不可變做爲前置條件的流程了。這樣作,其實也是在告訴咱們,若是咱們試圖修改它,就會報錯。

數據的不可變性總結

理解不可變性是很是重要的,咱們不能把不可變性框在一個很窄的範圍,好比上面的代碼,全局變量 sum 雖然是值類型,可能你以爲值類型是不可變的,可是你會發現,它也會被外界所修改。因此這裏的不可變性,並非小範圍的不可變,而是多個層面的不可變,這裏還須要多多去理解和感悟吧。

惰性

我認爲惰性不只僅是 FP 的特性,它在前端領域,也是一個很是重要的思想。

惰性能夠用一句話來解釋:

只在須要時才發生。

怎麼理解這句話呢,我來列舉一下,前端用到 惰性思想 的場景,場景以下:

  • 前端的懶加載
  • 前端的 tree shaking
  • 無限列表問題
  • recycle list
  • 動態導入導出
  • 前端的緩存

想一下,會發現, FP 的惰性目的,已經包含在上面那些場景中了。

關於 只在須要時才發生。 這句話, 小夥伴們能夠結合我列舉的場景進行對比理解,我就不繼續蒼白解釋了,點到爲止。

態射

爲何我要說這個,是由於理解態射的知識,對你更好的使用函數式編程有着很是重要的意義。

那什麼是態射呢?

簡單理解,就是一種狀態到另外一種狀態的映射。態射包含不少種形式,這裏咱們說一下類型映射。好比輸入一個 String 類型,而後返回一個 Boolean 類型,這就屬於一種態射。

好比下面的 demo

const isNumber = n => typeof n === 'number'
複製代碼

上面的函數,輸入的是數字類型,出入的是布爾類型,這就是一種態射形式。函數式編程的不少概念都和態射有關係,好比管道化、高階化、組合、鏈式等。如何將一種類型的數據機構映射成另外一種數據結構,這是一個很重要的知識點,這裏我把核心的點提了出來,小夥伴本身必定要多去了解和掌握。

高階性

高階性是函數式編程的一個核心思想之一,在更高抽象的實現上,必須依靠高階性來實現。經過把函數做爲參數,對當前函數注入解決特定任務的特定行爲,來代替充斥着臨時變量與反作用的傳統循環結構,從而減小所要維護以及可能出錯的代碼。

我認爲,高階性是一個很是重要的特性,想玩轉函數式編程,就必需要玩轉高階性。爲

爲何我要這樣說呢?

由於你會發現,函數的管道化,組合,柯里化,都是要依靠函數的高階性來實現。擁有高階性的函數,咱們稱它是高階函數,那麼什麼樣的函數成爲高階函數呢。

目前我理解的高階函數---HOC(Higher-Order-Functions)的定義是這樣的:

傳入的參數是函數,返回的結果是一個函數,這兩個條件,具有一個,就能夠稱函數是高階函數。

關於高階函數,更可能是去理解和實戰,這裏關於理論方面的知識我就很少介紹了,你們結合個人實戰篇好好理解一下。

其餘理論知識

上面我提到的理論知識,都是原子級別的特性,也算是 FP 的基石。像柯里化、偏應用、函數組合、函子等其餘更高階的實現,都是在前面這些 基石的基礎上實現的。因此這裏理論篇我就不介紹他們了,小夥伴們自行去分析和學習,我會在實戰篇把這些高級實現串起來。固然若是對這方面理論知識有興趣的小夥伴,想和我交流的,也能夠私聊我。

關於 FP 理論知識的總結

FP 是個好東西,它能夠幫助你控制代碼的複雜性,能夠加強你的邏輯能力,可讓測試更容易,等等。

如今,你們對比下面兩個問題:

  1. 學習算法對前端工程師有用嗎
  2. 學習函數式編程對前端工程師有用嗎

你會發現,都有用,但不表明咱們就要在實際前端場景中大量使用它們。

我的認爲前端須要掌握 FP 的水平

上面我介紹了我認爲目前 FP 領域內,須要掌握的一些元知識,只有掌握了這些元知識,咱們才能更好更快的掌握基於元知識衍生出的各類功能實現。好比柯里化函數、偏應用函數、組合函數、高階函數甚至函子等。

但其實,在我實踐了 FP 後,我我的認爲,如今的前端工程師在 FP 方面,只須要把組合,柯里化,和高階玩好就好了。其餘的高級用法能夠先不用進行實踐。我認爲前端工程師掌握這三個,就已經可以在前端把函數式思想發揮到淋漓盡致的地步了。這是在學習 FP 的感悟。

文末總結

原本想寫一篇的,可是發現一篇太長了,就分紅了上下兩篇。

函數式編程這塊,涉及到的東西很是多,光理論知識就不少了,個人這篇文章對於理論知識,沒有過多的去解釋和分析,我是按照我我的的理解和總結去向你們展現如何搞定函數式編程的理論知識。因此可能會有一些意見的不統一,或者理論知識涵蓋的不全,還請多多理解和支持。這篇文章不是單純的只分享如何搞定 FP ,是結合如何編寫高質量的函數去向你們進行綜合闡述。

參考

參考連接

參考書籍

  • JavaScript ES6 函數式編程入門經典
  • JavaScript 函數式編程指南
  • Haskell 趣學指南
  • 其餘電子書

交流

如何編寫高質量函數系列文章以下(不包含本篇):

能夠關注個人掘金博客或者 github 來獲取後續的系列文章更新通知。掘金系列技術文章彙總以下,以爲不錯的話,點個 star 鼓勵一下。

github.com/godkun/blog

我是源碼終結者,歡迎技術交流。

也能夠進 前端狂想錄羣 你們一塊兒頭腦風暴。有想加的,由於人滿了,能夠先加我好友,我來邀請你進羣。

風之語

最後:尊重原創,轉載請註明出處哈😋

相關文章
相關標籤/搜索