編程範式是一個由思考問題以及實現問題願景的工具組成的框架。不少現代語言都是聚範式(或者說多重範式): 他們支持不少不一樣的編程範式,好比面向對象,元程序設計,泛函,面向過程,等等。javascript
函數式編程就像一輛氫燃料驅動的汽車——先進的將來派,可是尚未被普遍推廣。與命令式編程相反,他由一系列語句組成,這些語句用於更新執行時的全局狀態。函數式編程將計算轉化做表達式求值。這些表達式全由純數學函數組成,這些數學函數都是一流的(能夠被當作通常值來運用和處理),而且沒有反作用。html
函數式編程很重視如下值:java
咱們應該將函數與編程語言中的其餘類對象一樣對待。換句話說,您能夠將函數存儲在變量裏,動態建立函數,以及將函數返回或者將函數傳遞給其餘函數。下面咱們來看一個例子…程序員
一個字符串能夠保存爲一個變量,函數也能夠,例如:算法
var sayHello = function() { return 「Hello」 };
一個字符串能夠保存爲對象字段,函數也能夠,例如:編程
var person = {message: 「Hello」, sayHello: function() { return 「Hello」 }};
一個字符串能夠再用到時才建立,函數也能夠,例如:數組
「Hello 」 + (function() { return 「World」 })(); //=> Hello World
一個字符串能夠做爲輸入參數傳給函數,則函數也能夠:安全
function hellloWorld(hello, world) { return hello + world() }
一個字符串能夠做爲函數返回值,函數也能夠,例如:數據結構
return 「Hello」; return function() { return 「Hello」};
若是函數將其餘函數函數做爲輸入參數或者做爲返回值,則稱之爲高階函數。剛纔咱們已經看過了一個高階函數的例子。下面,咱們來看一下更復雜的狀況。多線程
例1:
[1, 2, 3].forEach(alert); // alert 彈窗顯示「1" // alert 彈窗顯示 "2" // alert 彈窗顯示 "3」
例2:
function splat(fun) { return function(array) { return fun.apply(null, array); }; } var addArrayElements = splat(function(x, y) { return x + y }); addArrayElements([1, 2]); //=> 3
最愛純函數
純函數不會有其餘的反作用,所謂的反作用指的是函數所產生的對函數外界狀態的修改。好比:
最簡單的例子就是數學函數。Math.sqrt(4) 函數老是返回2。他不會用到任何其餘心寒信息,如狀態或者設置參數。數學函數歷來不會形成任何反作用。
函數式編程支持純粹的函數,這樣的函數不能改變數據,所以大多用於建立不可改變的的數據。這種方式,不用修改一個已存在的數據結構,並且能高效的新建一個.
你也許想知道,若是一個純粹的函數經過改變一些本地數據而生產一個不可改變的返回值,是不是容許的?答案是能夠。
在JavaScript中極少的數據類型是默認是不可改變的。String是一個不能被改變的數據類型的例子:
var s = "HelloWorld"; s.toUpperCase(); //=> "HELLOWORLD" s; //=> "HelloWorld"
• 避免混亂和增長程序的準確性:在複雜系統內,大多數難以理解的Bug是因爲狀態經過在程序中外部客戶端代碼修改而致使的。
• 確立「快速簡潔」的多線程編程:若是多線程能夠修改同一個共享值,你不得不一樣步的獲取值。這對專家來講都是十分乏味而且易出錯的編程挑戰。
軟件事務內存和Actor模型提供了直接在線程安全方式下處理修改。
遞歸是最有名的函數式編程技術。若是您還不知道它的話,那麼能夠理解爲遞歸函數就是一個能夠調用本身的函數。
替代反覆循環的最經典方式就是使用遞歸,即每次完成函數體操做以後,再繼續執行集合裏的下一項,直到知足結束條件。遞歸還天生符合某些算法實現,好比遍歷樹形結構(每一個樹枝都是一顆小樹)。
在任何語言裏,遞歸都是一項重要的函數式編程方式。不少函數語言甚至要求的更加嚴格:只支持遞歸遍歷,而不支持顯式的循環遍歷。這須要語言必須保證消除了尾端調用,這是 JavasSrip 不支持的。
數學定義了不少無窮集合,好比天然數(全部的正整數)。他們都是符號表示。任意特定有限的子集都在須要時求值。咱們將其稱之爲惰性求值(也叫作非嚴格求值,或者按需調用,延遲執行)。及早求值會強迫咱們表示出全部無窮數據,而這顯然是不可能的。
不少語言都默認是惰性的,有些也提供了惰性數據結構以表達無窮集合,並在須要時對本身進行精確計算。
很明顯一行代碼 result = compute() 所表達的是將 compute() 的返回結果賦值給 result。可是 result 的值到底是多少隻有其被用到的時候纔有意義。
可見策略的選擇會在很大程度上提升性能,特別是當用在鏈式處理或者數組處理的時候。這些都是函數式程序員所喜好的編程技術。
這就開創可不少可能性,包括併發執行,並行技術以及合成。
可是,有一個問題,JavaScrip 並不對自身進行惰性求值。話雖如此,Javascript 裏的函數庫能夠有效地模擬惰性求值。
全部的函數式語言都有閉包,然而這個語言特性常常被討論得很神祕。閉包是一個函數,這個函數有着對內部引用的全部變量的隱式綁定。換句話說,該函數對它引用的變量封閉了一個上下文。JavaScript 中的閉包是可以訪問父級做用域的函數,即便父級函數已經調用完畢。
function multiplier(factor) { return function(number) { return number * factor; }; } var twiceOf = multiplier(2); console.log(twiceOf(6)); //=> 12
函數式編程是聲明式的,就像數學運算,屬性和關係是定義好的。運行時知道怎麼計算最終結果。階乘函數的定義提供了一個例子:
factorial(n) = 1 if n = 1
n * factorial(n-1) if n > 1
該定義將 factorial(n) 的值關聯到 factorial(n-1),是遞歸定義。特殊狀況下的 factorial(1) 終止了遞歸。
var imperativeFactorial = function(n) { if(n == 1) { return 1 } else { product = 1; for(i = 1; i <= n; i++) { product *= i; } return product; } } var declarativeFactorial = function(n) { if(n == 1) { return 1 } else { return n * factorial(n - 1); } }
從它實現階乘計算來看,聲明式的階乘可能看起來像「命令式」的,但它的結構更像聲明式的。
命令式階乘使用可變值、循環計數器和結果來累加計算後的結果。這個方法顯式地實現了特定的算法。不像聲明式版本,這種方法有許多可變步驟,致使它更難理解,也更難避免 bug 。
有不少函數式庫:underscore.js, lodash,Fantasy Land, Functional.js, Bilby.js, fn.js, Wu.js, Lazy.js, Bacon.js, sloth.js, stream.js, Sugar, Folktale, RxJs 等等。
map(), filter(), 和 reduce()函數 構成了函數式程序員工具包的核心。 純高階函數成了函數式方法的主力。事實上,它們是純函數和高階函數應該仿效的典型。它們用一個函數做爲輸入,返回沒有反作用的輸出。