以前在網上看到了一篇教你如何用js寫出裝逼的代碼。javascript
通過學些以及擴展頗有收穫在這裏記錄一下。java
原文章找不到了。因此就不在這附上連接了。編程
你們看下下面兩段js代碼。設計模式
上面兩端代碼效果是如出一轍的,都是在一個指定的數組中,找到指定的數字所在的下標。第一個大多數人都看得懂。第二個就不必定了。數組
這裏就帶你們從一步步最初的版本演化到最終的函數式的版本。函數式編程
但願你們之後再遇到如此難以閱讀的代碼,知道怎麼去理解。函數
爲了從上面上面變到下面,咱們須要瞭解如下知識。性能
箭頭函數優化
三元運算符(不講)設計
尾遞歸優化
匿名函數(不講)
柯里化
高階函數
其中,尾遞歸優化,和柯里化,高階函數,都是函數式編程裏面的東西。因此有必要先簡單介紹一下什麼是函數式編程。
這裏先引用下知乎的圖。
上面的圖並非說,最下面的最高級,而是越靠下面的越費腦子。
什麼是函數式編程?
事實上函數式編程是從範疇論(category theory)發展過來的。而範疇論其實和微積分,數理邏輯,等等都是一種數學理論。而函數式編程只是參考這種思想發展過來的,就像設計模式最初是來源於建築學同樣。
爲了更好的理解函數式編程,這裏也再簡單的介紹下範疇論。高深的我也不懂。。。
在維基百科裏面是這麼定義的
也就是說只要是存在某種關係,能夠從一個對象轉化爲另一個對象,那麼對象和它們之間的關係就構成一個範疇。
下面的圖是一個示意圖。
紅色的點和黃色的箭頭在一塊兒就構成一個範疇。
箭頭arrow,還有一個正式的名字叫作態射(morphism)。範疇論認爲,同一個範疇的全部成員,就是不一樣狀態的"變形"(transformation)。經過"態射",一個成員能夠變造成另外一個成員。
上面的很抽象,咱們舉例子說明下。
你們都對面向對象的思想比較瞭解,在面向對象的思想裏,萬事萬物都是對象,而對象又能夠抽象成爲類。好比,黃種人,白種人,黑人。都是對象,能夠抽象爲人這個類。這是他們有共性。可是他們之間並不存在轉化關係,黑人不可能轉化爲白種人,就算他整容整的很白,概念上他仍是個黑人。
而數字1,2,3,4,5。。。他們之間存在必定的關係,1+1能夠變成2,2+1可變成3,2+2又能夠變成4。咱們能夠認爲,數字和它們之間的態射就是一個範疇。每個數字,經過一個態勢能夠變爲另一個數字。
因此範疇包含兩部分
而過分到程序裏面就是
簡單的來講就是,一個值能夠經過一個函數變爲另一個值。因此函數式編程要求每個函數必須是乾淨的,進入一個值,出去另一個值。不會操做任何方法外的數據。
函數式編程有兩個最基本的運算。
合成的概念就是若是一個值須要通過多個函數才能變成另一個值,就能夠把兩個函數合成一個函數。好比1,須要通過add1和add2才能變成4,那麼就能夠合成一個add3出來。
假設add1=f,add2=g
上面看起來函數有點多,那咱們把add1和add2都給匿名了,看起來會好一點
函數的合成還要知足結合律
假設f,g,h分別是add1,add2,add3.
那麼(h·g).f 就是 add3(add1(add2)) 而h·(g·f) 就是add1(add2(add3))
他們二者應該是相等的。
2.柯里化
f(x)和g(x)合成爲f(g(x)),有一個隱藏的前提,就是f和g都只能接受一個參數。若是能夠接受多個參數,好比f(x, y)和g(a, b, c),函數合成就很是麻煩。
這時就須要函數柯里化了。所謂"柯里化",就是把一個多參數的函數,轉化爲單參數函數。
這裏解釋一下,柯里化以後是採用的js裏面的鏈式調用。AddX(2)其實返回的是function(x)
{return x+2;} 這時候再跟一個(1),就是把1傳入裏面執行了。
固然函數式編程還有不少別的東西,這裏就不一一介紹了,有興趣的的能夠本身查下。
下面說下, 尾遞歸優化
咱們知道遞歸的害處,那就是若是遞歸很深的話,stack受不了,並會致使性能大幅度降低。因此,咱們使用尾遞歸優化技術——每次遞歸時都會重用stack,這樣一來可以提高性能,固然,這須要語言或編譯器的支持。Java 就不支持,可是javascript支持。而所謂的支持,就是說編譯器會自動優化,對於尾遞歸的代碼會自動優化成。
普通遞歸。
下面是入棧和出棧的過程。會保存上一步的計算狀態,太深的話就會棧溢出。
fac(5)
(5*fac(4))
(5*(4*fac(3)))
(5*(4*(3*fac(2))))
(5*(4*(3*(2*fac(1)))))
(5*(4*(3*2)))
(5*(4*(6)))
(5*24)
120
尾遞歸
當遞歸調用是整個函數體中最後執行的語句且它的返回值不屬於表達式的一部分時,這個遞歸調用就是尾遞歸
每次都是執行一個獨立的函數,和以前的函數並無關聯。
fac(5,1)
fac(4,5)
fac(3,20)
fac(2,60)
fac(1,120)
120
普通遞歸建立stack累積然後計算收縮,尾遞歸只會佔用恆量的內存。只須要保存每次計算出來的值,而後傳入同一個函數就好。
高階函數。
高階函數就是函數當參數,把傳入的函數作一個封裝,而後返回這個封裝函數。上面一直都在用到。
箭頭函數
ECMAScript2015 引入的箭頭表達式。箭頭函數其實都是匿名函數。簡單的理解就是經過箭頭建立函數。
一個參數時候能夠省略小括號,方法體只有一句的時候能夠省略大括號
Function (x){return x+1}; 等價於(x) =>{return x+1}或者x=>return x+1;
無參數必須有括號。
Function (){return 1+1} 等價於 ()=>{return 1+1}
若是想加名字的話。
Var add = ()=>{return 1+1}; 調用 add() 就會返回2
該介紹的都介紹了,下面就一步步改造。
原始版本
尾遞歸優化以後
替換三元運算符
函數體內的函數參數化
轉化爲箭頭函數
匿名
引入高階函數,並柯里化。
爲了方便調用再加上名字
下面斷點圖幫助理解。