Javascript函數式編程小結

源起

函數式編程近幾年很是流行,常常能夠在網上看到別人討論相關話題. 我機緣巧合之下在github上看到有人提到一個講js函數式編程的視頻,看過以後忽然茅塞頓開,瞬間把以前零碎的關於函數式編程的知識一會兒都聯繫了起來, 因而本身但願趁有空把這些知識總結一下, 這樣既能夠回顧下知識耶沒準能幫到一些對函數式編程感興趣的朋友們.javascript

爲何須要函數式編程

其實千萬別被這看似高深的名字嚇怕了, 其實咱們碼農用這種編程思想無非是爲了能更爽的寫好代碼. 總結一下函數式編程的優勢有如下兩個優勢:html

  1. Less bug: 可讀性高, 邏輯上更容易理解.java

  2. less Time: 更高的抽象層級, 代碼高度可複用.git

高階函數(Higher-order functions)

這裏又一個炫酷屌的名字: "高階函數". 別怕, 本文並不涉及高等數學的知識就可讓你瞭解高階函數的概念. 可是等等! 高階函數跟函數式編程又有毛關係? 其實簡單的說就是高階函數的特性讓咱們能夠實現函數式編程.
廢話很少說, 下面就簡單說下什麼是高階函數:github

Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions.算法

從定義能夠知道高階函數就是能夠把函數做爲變量傳入別的函數, 或者一個函數的返回值是一個函數的函數. 若是上面的話把你繞暈了那麼我來簡單的歸納下就是函數能夠被看成變量在程序中傳來傳去. 下面舉幾個例子:編程

//把函數當成變量
var foo = (x) => console.log(x)
//把函數看成參數傳入另外一個函數
var bar = (y, fn) => fn(y)
foo('FP is good') // FP is good
bar('FP is great', foo) //FP is great

下面這個例子更能體現高階函數的便利性, 若是咱們要遍歷數組而且打印數組元素的信息, 你會怎麼作?數組

var arr = [1,1,2,3,5,8]
function traverseArray(arr) {
  for (let i = 0; i < arr.length; i++) {
    console.log(`index: ${i}, value: ${arr[i]}`)
  }
}
traverseArray(arr)

Easy enough! 若是用函數式編程的思惟重寫上面的代碼應該是這個樣子的:less

function forEach(arr, fn) {
  for (let i = 0; i < arr.length; i++) {
    fn(i, arr[i], arr)
  }
}

forEach(arr, (index, value) => console.log(`index: ${index}, value: ${value}`))

WTF!? 說好的函數式編程更簡潔代碼量更少呢! 明顯第二種寫法寫的代碼更多啊. 別急, 其實es5的時候JS已經把一系列高階函數設置好了,好比forRach, map, reduce, filter, every.. 讓咱們用js的array.forEach重寫上面的代碼:函數式編程

arr.forEach((v, k) => console.log(`index: ${k}, value: ${v}`))

怎麼樣, 同樣代碼解決戰鬥。 也許看到這裏你仍是以爲沒什麼意思,畢竟雖然代碼量少了些,但也沒感受有什麼本質性的變化. 下面我會舉幾個比較常見的例子,經過例子讓咱們一塊兒體會這種有趣的編程思想,也許看過以後你的想法會有所改變.

經常使用方法舉例

先介紹下幾個經常使用的函數, filter, map, reduce:

var animals = [
  {name: 'a' , species: 'dog', weight: 11},
  {name: 'b', species: 'cat', weight: 10},
  {name: 'c', species: 'fish', weight: 1},
  {name: 'd', species: 'cat', weight: 8},
  {name: 'e', species: 'rabbit', weight: 3}
]
// 找到全部種類爲貓的動物
animals.filter((animal) => animal.species === 'cat')
// [ { name: 'b', species: 'cat' }, { name: 'd', species: 'cat' } ]

// 返回全部動物的名字
animals.map((animal) => animal.name)
// [ 'a', 'b', 'c', 'd', 'e' ]

// 動物總重
animals.reduce((pre, animal) => pre + animal.weight, 0)
//33

怎麼樣? 若是用循環來寫確定不可能一行代碼就搞定吧. 讓咱們看一些更深刻更有意思的例子吧.
好比這道比較經典的算法題: 給定一個字符串"abcdaabc"統計每一個字符的出現次數.

// 通常作法是這樣的
var str="abcdaabc"
var count = {};
var i;

for(i=0;i<str.length;i++){
    var chr = str.charAt(i);
    if( typeof count[chr] === "undefined"){
        count[chr] = 1;
    }else{
        count[chr]++;
    }
}

// 利用函數式編程思想的方法是這樣的
var res = str.split('')
             .reduce((pre, cur) => (pre[cur]++ || (pre[cur] = 1), pre), {})

由上面的例子可見, 高階函數的特性讓咱們在處理這類問題時比普通的方法要方便的多. 在更實際的應用場景中函數式編程的便利性更能充分的體現, 其中一個經典的例子是統計文本中出現頻率最高的十個單詞:

var fs = require('fs');
var content = fs.readFileSync('words.txt').toString();
var words = content.split(/[\s.,\/:\n]+/);
// 把單詞所有變爲小寫並利用上一個例子的方法統計單詞出現的次數
var tally = words.map((word) => word.toLowerCase())
                 .reduce((pre, cur) => (pre[cur]++ || (pre[cur]=1), pre), {})
//把object的key變成數組並進行排序
var top10 = Object.keys(tally)
                  .map((key) => {
                    return {word: key, count: tally[key]}
                  })
                  .sort((a, b) => b.count - a.count)
                  .slice(0, 10)
console.log(top10)

上面這個例子充分體現了函數式編程思想的魅力,代碼精簡而且可讀性高! 想想若是你要用循環來寫有多多少行代碼!
好了, 總結完畢, 最後附上參考資料.

參考資料

Functional programming in JavaScript
Functional Programming By Example
Eloquent javascript - chapter6

相關文章
相關標籤/搜索