JavaScript與函數式編程 絕大多數編程語言都會有函數的概念(或者說所有的?我不太確定),他們都可以做出類似的操作: 但是Javascript更適合函數式編程,因為函數對於js來說,是 一等公民 。 我們可以把匿名函數賦值給一個變數,比如: 然後我們可以將這個函數賦值給另一個變數: 這樣做和直 ...
JavaScript與函數式編程
絕大多數編程語言都會有函數的概念(或者說所有的?我不太確定),他們都可以做出類似的操作:
function(x) {
return x * x
}
但是Javascript更適合函數式編程,因為函數對於js來說,是一等公民。
我們可以把匿名函數賦值給一個變數,比如:
let pow = function(x) {
return x * x
}
然後我們可以將這個函數賦值給另一個變數:
let comeon = pow
comeon(x)
這樣做和直接調用pow(x)
是一樣的效果。
甚至於我們可以將函數作為參數傳入另一個函數,這樣,諸多小函數可以匯聚在一起,變得異常強大!
filter()
OK,我們接下來看一些比較基礎的例子。
首先是filter()
,這是我最喜歡的函數之一。filter()
方法會創建一個新的數組,並且我們可以傳入一個判斷函數,將符合條件的元素,放入新的數組。
現在我們有一個數組,裡面存放了很多游戲,每個元素都記錄了該游戲需要花多少錢:
let games = [
{
name: '英雄聯盟',
cost: 45
},
{
name: '穿越火線',
cost: 888
},
{
name: '魔獸世界',
cost: 75
},
{
name: '征途',
cost: 1000000
}
]
然後我們需要找出,花費不超過一百元的游戲,該怎麼做呢?
可能是這樣:
let target = []
for(let i = 0; i < games.length; i++) {
if(games[i].cost <= 100) {
target.push(games[i])
}
}
這是大家從大學開始學C語言的時候就會用的方法。但是我現在想用filter()
方法重寫它:
let target = games.filter((game) => {
return game.cost <= 100
})
Wow! Awesome!
我稍微解釋一下,防止有同學沒有看懂,這裡傳入了一個函數,函數接收了一個參數,這個參數就是games的每一個元素依次傳入的值,在每一次傳入之後,我們都返回一個邏輯值,這個邏輯值取決於該游戲的cost
是否小於100,如果返回了true
該元素就會被放到新的數組裡去,反之同理。
註意:
filter()
不會對空數組進行檢測filter()
不會改變原始數組
實際上,filter內部的處理方法可能和我們使用for迴圈一模一樣!但是我們利用函數式編程,寫了更少的代碼、更少的邏輯。Less code! Less time! Less bug!
這就是函數式編程的美妙之處。
map()
我們現在先看回之前寫的數組:
let games = [
{
name: '英雄聯盟',
cost: 45
},
{
name: '穿越火線',
cost: 888
},
{
name: '魔獸世界',
cost: 75
},
{
name: '征途',
cost: 1000000
}
]
現在我們要把每一款游戲的名字都拿出來,組成一個新的數組。
這種操作是非常常見的,比如我們使用React,向伺服器請求了JSON數據,接下來需要在這裡渲染名字,那裡渲染價格……等等。
let gameName = games.map((item) => {
return item.name
})
我們甚至可以做出更多的騷操作,比如說:
let gameName = games.map((item) => {
return `${item.name}是一個${item.cost > 100? '坑錢游戲':'良心游戲'}`
})
得到結果["英雄聯盟是一個良心游戲", "穿越火線是一個坑錢游戲", "魔獸世界是一個良心游戲", "征途是一個坑錢游戲"]
Wow! Awesome! 只能用優雅兩個字來形容!
reduce()
reduce()
單詞本身是減少的意思,但是實際上你可以將reduce()
理解為求和(事實並不如此,reduce更加強大且靈活,但是此時可以暫時這麼理解,更多特性可以在下一節看到)。
語言太過蒼白無力,我們來看看代碼:
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce((total, item) => total + item)
這裡是利用了箭頭函數可以省略return的特性。
這裡的傳入的函數接收了兩個參數(實際上可以接收四個,但這裡不需要後面兩個),這兩個參數通過英文應該就可以看懂。
reduce()
方法接收一個函數作為累加器,數組中的每個值(從左到右)開始縮減,最終計算為一個值。
註意: reduce()
對於空數組是不會執行回調函數的。
這裡可以給一個初始值:
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce((total, item) => {
return total + item
}, 10)
之前的和是15,這次加上了一個初始值,就是25了。
實踐
假設我們有一個TXT文件。
徐航宇 游泳 4分鐘
徐航宇 跑步 1分鐘
徐航宇 跳遠 3米
劉好 游泳 5分鐘
劉好 跑步 2分鐘
劉好 跳遠 1米
我們用node去讀取他,讓它變成一個JSON對象,就像這樣:
{
"徐航宇": [
{
"項目": "游泳",
"時間": "4分鐘"
},
{
"項目":"跑步",
"時間":"1分鐘"
}
],
"劉好": [
// ...
]
}
上代碼!
import fs from 'fs'
let output = fs.readFileSync('data.txt', 'utf-8')
.trim() // 去掉字元串頭尾的空格,返回新數組
.split('\n') // 在換行處截斷,組成數組
.map(line => line.split('\t')) // 每一行根據製表符斷開,組成數組
這一步之後,我們應該得到什麼?
[
["徐航宇", "游泳", "4分鐘"],
["徐航宇", "跑步", "1分鐘"],
["徐航宇", "跳遠", "3米"],
["劉好", "游泳", "5分鐘"],
["劉好", "跑步", "2分鐘"],
["劉好", "跳遠", "1米"]
]
接下來想要變成對象,該怎麼做呢?
首先第一步,我們想一想,要想變成最終的JSON數據,我們需要對每一項進行處理:
- 把每一個數組的第一個作為對象的屬性名
- 把每一個數組的二三項組成新的對象,放入該屬姓名的值中
接下來就該reduce()
出場了:
.reduce((customer, line) => {
// 提取每個數組的第一項,作為傳入對象的屬姓名
// 如果該項是以前沒有的,那麼將其初始化為空數組
// 如果該項以前有,那麼就不動他
customer[line[0]] = customer[line[0]] === undefined ? [] : customer[line[0]]
customer[line[0]].push({
name: line[1],
time: line[2]
})
return customer
}, {})
這樣就成功了!
總結
總而言之,函數式編程如果歸根結底,和直接寫沒有任何區別。但是它提供給了我們一種寫更少的代碼,完成更多的事情的方法。
人是難免會出錯的,代碼量越大、錯誤可能就會越多,所以更少代碼的函數式編程,往往意味著:更少的bug。
(完)