函數式編程是React的精髓,在正式講解React以前,有必要先了解一下函數式編程,有助於更好的理解React的特色。函數式編程(Functional Programming)不是一種新的框架或工具,而是一種以函數爲主的編程範式。編程範式也叫編程範型,是一類編程風格,除了函數式編程,經常使用的還有面向對象編程、命令式編程等。git
聲明時編程也是一種範式,但它是一個比較大的概念,函數式編程是它的一個子集。聲明式編程能指定每一步操做,而不用向計算機描述具體的實現細節。與之相對立的是命令式編程,它會命令計算機每一步該怎麼作。以數組的元素翻倍爲例,先用命令式編程實現,以下所示。編程
var arr = [1, 2, 3], length = arr.length, doubles = []; for (let i = 0; i < length; i++) { doubles.push(arr[i] * 2); }
在命令式的代碼中,先用for循環遍歷整個數組,而後讓每一個元素乘以二,再將計算結果插入到doubles數組中,直至將全部的元素計算完才終止整套操做。改用聲明式編程能夠像下面這樣實現相同的功能。數組
var doubles = [1, 2, 3].map(value => value * 2);
在聲明式的代碼中,用map()方法替代了循環語句(即不指明流程的控制方式),既不用再維護計數器,也不用再經過索引訪問數組的元素,配合ES6的箭頭函數讓整套操做變得很是簡潔。cookie
除了這些表面區別以外,還有個最本質的區別,那就是聲明式編程會避免用變量保存程序的狀態,從而能提升代碼的無狀態性。在命令式的代碼中,每次迭代都會修改doubles變量,這是個狀態變量,而在聲明式的代碼中,改用返回值保存程序的狀態。框架
函數式編程強調在程序中使用函數。因爲JavaScript中的函數是一等公民,它既能夠是變量的值,也能夠做爲另外一函數的參數或返回值,所以經過函數可構建一層抽象以替代流程控制或解決複雜的邏輯操做。例如對數組中的數字進行排序和過濾,能夠像下面這樣運用函數式編程的思想實現。dom
[4, 1, 5, 2, 3].sort((a, b) => a > b).filter(value => value > 2); //[3, 4, 5]
函數式編程旨在將複雜的運算分解成一系列嵌套的函數,逐層推導,不斷漸進,直至完成運算。模塊化
純函數(Pure Function)是一種沒有反作用、引用透明的函數,它是函數式編程的基本概念,接下來會重點講解它的三個特徵。函數式編程
1)函數的反作用函數
函數在讀寫外部資源或執行不肯定的操做時就會產生反作用,例如修改函數外的變量、調用Date.now()或Math.random()、更新cookie信息等。反作用不只會下降程序總體的可讀性,有時候還會帶來意料以外、難以排查的錯誤,下面是一個反作用的例子。工具
var digit = 1; function increment() { digit += Math.random(); return digit; }
在上面的代碼中,increment()函數產生了反作用,由於每次調用它都會更新外部的digit變量,而且每次獲得的計算結果也沒法預知。
2)引用透明
若是傳遞給函數相同的參數,始終能獲得相同的結果,那麼就能說這個函數是引用透明(Referential Transparent)的。簡單的說就是,函數的運行只受其輸入值的影響,以下代碼所示,傳遞給add()函數固定的參數會返回固定的值。
function add(a, b) { return a + b; }
3)參數值不可變
傳遞給純函數的參數值是不容許在內部將其改變的,換句話說,在函數內部使用的是參數值的副本。若是參數值是基本類型的,那麼傳遞給函數的就是其副本;但若是參數值是對象類型的,那麼須要注意,傳遞給函數的是引用對象的指針。
下面用一個示例說明,addDigit()函數的參數是一個數組,它的功能是爲數組的每一個元素加一,在執行addDigit(digits)以後,因爲digits變量是一個數組,所以它的元素會隨着函數的調用而被改變。
var digits = [1, 2, 3]; function addDigit(arr) { for (let i = 0, len = arr.length; i < len; i++) { arr[i] += 1; } return arr; } addDigit(digits); console.log(digits); //[2, 3, 4]
接下來修改addDigit()函數,使之能知足純函數的要求,以下所示。
var digits = [1, 2, 3]; function addDigit(arr) { return arr.map(value => value + 1); } addDigit(digits); console.log(digits); //[1, 2, 3]
在addDigit()函數內部,用map()方法替代for循環,使得在不改變參數的前提下,完成元素加一的功能。
函數式編程有許多優勢,本節只列出了其中的兩點。
(1)函數式編程可將複雜的任務分解成一個個既簡單又獨立的純函數,有利於提升代碼的模塊化、複用性、預測性以及可測試性。
(2)函數式編程有很高的自由度,能夠採用更符合人類思惟習慣的鏈式寫法,以此提升代碼的可讀性。
接下來會用兩種函數式的寫法操做一個數組,爲了便於演示省略了函數的具體實現,首先是普通的函數式寫法,以下所示。
elementDouble(filterEven(arr, filterFn), doubleFn);
兩個函數都有兩個參數,第一個是數組,第二個是相應的回調函數。具體的執行過程是先經過filterEven()函數過濾掉數組中偶數位置的元素,再用elementDouble()函數把每一個元素翻倍,下面改爲鏈式的寫法。
filerEven(arr, filterFn).elementDouble(arr, doubleFn);
經過兩段代碼的對比能夠看出,鏈式的寫法更容易讓人理解,代碼意圖也更清晰。