到目前為止,我們在本書中所用的編程範式都是命令式編程。在命令式編程中,我們按部就班地編寫程序代碼,詳細描述要完成的事情以及完成的順序。
在本節中,我們會介紹一種新的範式,叫作函數式編程。函數式編程是一種曾經主要用於學術領域的範式,多虧了Python和Ruby等現代語言,它才開始在行業開發者中流行起來。值得欣慰的是,借助ES6的能力,JavaScript也能夠進行函數式編程。
11.4.1 函數式編程與命令式編程
以函數式範式進行開發並不簡單;關鍵在於習慣這種範式的機制。我們編寫一個例子來說明差異。
假設我們想打印一個數組中所有的元素。我們可以用命令式編程,聲明的函數如下:
var printArray = function(array) {
for (var i = 0; i < array.length; i++) {
console.log(array[i]);
}
};
printArray([1, 2, 3, 4, 5]);
在上面的代碼中,我們迭代數組,打印每一項。
現在,我們試著把這個例子轉換成函數式編程。在函數式編程中,函數就是搖滾明星。我們關注的重點是需要描述什麼,而不是如何描述。回到這一句:「我們迭代數組,打印每一項」。那麼,我們首先要關注的是迭代數據,然後進行操作,即打印數組項。下面的函數負責迭代數組:
var forEach = function(array, action) {
for (var i = 0; i < array.length; i++) {
action(array[i]);
}
};
接下來,我們要創建另一個負責把數組元素打印到控制台的函數(考慮為回調函數),如下:
var logItem = function (item) {
console.log(item);
};
最後,像下面這樣使用聲明的函數:
forEach([1, 2, 3, 4, 5], logItem);
只需要上面這一行代碼,我們就能描述我們要把數組的每一項打印到控制台。這是我們的第一個函數式編程的例子!
有幾點要注意:
主要目標是描述數據,以及要對數據應用的轉換;
程序執行順序的重要性很低,而在命令式編程中,步驟和順序是非常重要的;
函數和數據集合是函數式編程的核心;
在函數式編程中,我們可以使用和濫用函數和遞歸,而在命令式編程中,則使用循環、賦值、條件和函數。
11.4.2 ES2015和函數式編程
有了ES2015的新功能,用JavaScript進行函數式編程變得更加容易了。我們來看一個例子。
考慮我們要找出數組中最小的值。要用命令式編程完成這個任務,只要迭代數組,檢查當前的最小值是否大於數組元素;如果是,就更新最小值,代碼如下:
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
用函數式編程完成相同的任務,可以使用Math.min
函數,傳入所有要比較的數組元素。我們可以像下面的例子裡這樣,使用ES2015的解構操作符(...
),把數組轉換成單個的元素:
const min_ = function(array) {
return Math.min(...array)
};
console.log(min_([8, 6, 4, 5, 9])); //輸出4
使用ES2015的箭頭函數,我們可以進一步簡化上面的代碼:
const min = arr => Math.min(...arr);
console.log(min([8, 6, 4, 5, 9]));
11.4.3 JavaScript函數式工具箱—— map
、filter
和reduce
map
、filter
和reduce
函數(第2章已經學習過)是函數式編程的基礎。
我們可以使用map
函數,把一個數據集合轉換或映射成另一個數據集合。先看一個命令式編程的例子:
var daysOfWeek = [
{name: \'Monday\', value: 1},
{name: \'Tuesday\', value: 2},
{name: \'Wednesday\', value: 7}
];
var daysOfWeekValues_ = ;
for (var i = 0; i < daysOfWeek.length; i++) {
daysOfWeekValues_.push(daysOfWeek[i].value);
}
再以函數式編程來考慮同樣的例子,代碼如下:
var daysOfWeekValues = daysOfWeek.map(function(day) {
return day.value;
});
console.log(daysOfWeekValues);
我們可以使用filter
函數過濾一個集合的值。來看一個例子:
var positiveNumbers_ = function(array) {
var positive = ;
for (var i = 0; i < array.length; i++) {
if (array[i] >= 0) {
positive.push(array[i]);
}
}
return positive;
}
console.log(positiveNumbers_([-1, 1, 2, -2]));
我們可以把同樣的代碼寫成函數式的,如下:
var positiveNumbers = function(array) {
return array.filter(function(num) {
return num >= 0;
})
};
console.log(positiveNumbers([-1, 1, 2, -2]));
我們也可以使用reduce
函數,把一個集合歸約成一個特定的值。比如,對一個數組中的值求和:
var sumValues = function(array) {
var total = array[0];
for (var i = 1; i < array.length; i++) {
total += array[i];
}
return total;
};
console.log(sumValues([1, 2, 3, 4, 5]));
上面的代碼也可以寫成這樣:
var sum_ = function(array) {
return array.reduce(function(a, b) {
return a + b;
})
};
console.log(sum_([1, 2, 3, 4, 5]));
我們還可以把這些函數與ES2015的功能結合起來,比如解構操作符和箭頭函數,代碼如下:
const sum = arr => arr.reduce((a, b) => a + b);
console.log(sum([1, 2, 3, 4, 5]));
我們再看另一個例子。考慮我們需要寫一個函數,把幾個數組連接起來。為此,可以創建另一個數組,用於存放其他數組的元素。我們可以執行以下命令式的代碼:
var mergeArrays_ = function(arrays) {
var count = arrays.length,
newArray = ,
k =0;
for (var i = 0; i < count; i++) {
for (var j = 0; j < arrays[i].length; j++) {
newArray[k++] = arrays[i][j];
}
}
return newArray;
};
console.log(mergeArrays_([[1, 2, 3], [4, 5], [6]]));
注意,在這個例子中,我們聲明了變量,還使用了循環。現在,我們用JavaScript函數式編程把上面的代碼重寫如下:
var mergeArraysConcat = function(arrays) {
return arrays.reduce(function(p, n) {
return p.concat(n);
});
};
console.log(mergeArraysConcat([[1, 2, 3], [4, 5], [6]]));
上面的代碼完成了同樣的任務,但它是面向函數的。我們也可以用ES2015使代碼更加精簡,如下所示:
const mergeArrays = (...arrays) => .concat(...arrays);
console.log(mergeArrays([1, 2, 3], [4, 5], [6]));
從11行代碼變成了只有一行!
如果你想更多地練習JavaScript函數式編程,可以試試這些習題,非常有意思:http://reactivex.io/learnrx/。
11.4.4 JavaScript函數式類庫和數據結構
有一些很棒的JavaScript類庫借助工具函數和函數式數據結構,對函數式編程提供支持。通過下面的列表,你可以找到一些最有名的JavaScript函數式類庫。
Underscode.js:http://underscorejs.org/
Bilby.js:http://bilby.brianmckenna.org/
Lazy.js:http://danieltao.com/lazy.js/
Bacon.js:https://baconjs.github.io/
Fn.js:http://eliperelman.com/fn.js/
Functional.js:http://functionaljs.com/
Ramda.js:http://ramdajs.com/0.20.1/index.html
Mori:http://swannodette.github.io/mori/
如果你對學習JavaScript函數式編程感興趣,可以看看Packt出版的另一本書:https://www.packtpub.com/web-development/functional-programming-javascript。