函數(shù)式編程與命令式編程
以函數(shù)式范式進(jìn)行開(kāi)發(fā)并不簡(jiǎn)單,關(guān)鍵在于習(xí)慣這種范式的機(jī)制。我們編寫一個(gè)例子來(lái)說(shuō)明差異。
假設(shè)我們想打印一個(gè)數(shù)組中所有的元素。我們可以用命令式編程,聲明的函數(shù)如下。
const printArray = function(array) {
for (var i = 0; i < array.length; i++){
console.log(array[i]);
}
};
printArray([1, 2, 3, 4, 5]);
在上面的代碼中,我們迭代數(shù)組,打印每一項(xiàng)。
現(xiàn)在,我們?cè)囍堰@個(gè)例子轉(zhuǎn)換成函數(shù)式編程。在函數(shù)式編程中,函數(shù)就是搖滾明星。我們關(guān)注的重點(diǎn)是需要描述什么,而不是如何描述?;氐竭@一句:“我們迭代數(shù)組,打印每一項(xiàng)?!蹦敲?,首先要關(guān)注的是迭代數(shù)據(jù),然后進(jìn)行操作,即打印數(shù)組項(xiàng)。下面的函數(shù)負(fù)責(zé)迭代數(shù)組。
const forEach = function(array, action){
for (var i = 0; i < array.length; i++){
action(array[i]);
}
};
接下來(lái),要?jiǎng)?chuàng)建另一個(gè)負(fù)責(zé)把數(shù)組元素打印到控制臺(tái)的函數(shù)(考慮為回調(diào)函數(shù)),如下所示。
const logItem = function(item) {
console.log(item);
};
最后,像下面這樣使用聲明的函數(shù)。
forEach([1, 2, 3, 4, 5], logItem);
只需要上面這一行代碼,就能描述我們要把數(shù)組的每一項(xiàng)打印到控制臺(tái)。這是我們的第一個(gè)函數(shù)式編程的例子!
有以下幾點(diǎn)要注意。
- 函數(shù)式編程的主要目標(biāo)是描述數(shù)據(jù),以及要對(duì)數(shù)據(jù)應(yīng)用的轉(zhuǎn)換。
- 在函數(shù)式編程中,程序執(zhí)行順序的重要性很低;而在命令式編程中,步驟和順序是非常重要的。
- 函數(shù)和數(shù)據(jù)集合是函數(shù)式編程的核心。
- 在函數(shù)式編程中,我們可以使用和濫用函數(shù)和遞歸;而在命令式編程中,則使用循環(huán)、賦值、條件和函數(shù)。
- 在函數(shù)式編程中,要避免副作用和可變數(shù)據(jù),意味著我們不會(huì)修改傳入函數(shù)的數(shù)據(jù)。如果需要基于輸入返回一個(gè)解決方案,可以制作一個(gè)副本并返回?cái)?shù)據(jù)修改后的副本。
ES2015+和函數(shù)式編程
有了ES2015+的新功能,用JavaScript進(jìn)行函數(shù)式編程就變得更加容易了。我們來(lái)看一個(gè)例子。
考慮我們要找出數(shù)組中最小的值。要用命令式編程完成這個(gè)任務(wù),只要迭代數(shù)組,檢查當(dāng)前的最小值是否大于數(shù)組元素;如果是,就更新最小值,代碼如下。
var findMinArray = function(array){
var minValue = array[0];
for (var i=1; i<array.length; i++){
if (minValue > array[i]){
minValue = array[i];
}
}
return minValue;
};
console.log(findMinArray([8,6,4,5,9])); // 輸出4
要用函數(shù)式編程完成相同的任務(wù),可以使用Math.min函數(shù),傳入所有要比較的數(shù)組元素。我們可以像下面的例子里這樣,使用ES2015的解構(gòu)運(yùn)算符(...),把數(shù)組轉(zhuǎn)換成單個(gè)的元素。
const min_ = function(array){
return Math.min(...array)
console.log(min_([8,6,4,5,9])); // 輸出4
使用ES2015的箭頭函數(shù),可以進(jìn)一步簡(jiǎn)化上面的代碼。
const min = arr => Math.min(...arr);
console.log(min([8, 6, 4, 5, 9]));
JavaScript函數(shù)式工具箱——map、filter和reduce
map、filter和reduce函數(shù)(第3章已經(jīng)學(xué)習(xí)過(guò))是JavaScript函數(shù)式編程的基礎(chǔ)。
我們可以使用map函數(shù),把一個(gè)數(shù)據(jù)集合轉(zhuǎn)換或映射成另一個(gè)數(shù)據(jù)集合。先看一個(gè)命令式編程的例子。
const daysOfWeek = [
{name: 'Monday', value: 1},
{name: 'Tuesday', value: 2},
{name: 'Wednesday', value: 7}
];
let daysOfWeekValues_ = [];
for (let i = 0; i < daysOfWeek.length; i++) {
daysOfWeekValues_.push(daysOfWeek[i].value);
}
再以函數(shù)式編程并使用ES2015+語(yǔ)法來(lái)考慮同樣的例子,代碼如下。
const daysOfWeekValues = daysOfWeek.map(day => day.value);
console.log(daysOfWeekValues);
我們可以使用filter函數(shù)過(guò)濾一個(gè)集合的值。下面來(lái)看一個(gè)例子。
const positiveNumbers_ = function(array){
let positive = [];
for (let i = 0; i < array.length; i++) {
if (array[i] >= 0){
positive.push(array[i]);
}
}
return positive;
}
console.log(positiveNumbers_([-1,1,2, -2]));
我們可以把同樣的代碼寫成函數(shù)式的,如下所示。
const positiveNumbers = (array) => array.filter(num => (num >= 0));
console.log(positiveNumbers([-1,1,2, -2]));
我們也可以使用reduce函數(shù),把一個(gè)集合歸約成一個(gè)特定的值。比如,對(duì)一個(gè)數(shù)組中的值求和。
const sumValues = function(array) {
let total = array[0];
for (let i = 1; i<array.length; i++) {
total += array[i];
}
return total;
};
console.log(sumValues([1, 2, 3, 4, 5]));
上面的代碼也可以寫成這樣。
const sum_ = function(array){
return array.reduce(function(a, b){
return a + b;
})
};
console.log(sum_([1, 2, 3, 4, 5]));