背景
函數式編程是一種編程思想,是編程範式的一種,其餘的還有命令式(指令式)編程和麪向對象編程。
函數式舉例
假設我們要把字符串 functional programming is great
變成每個單詞首字母大寫
var string = 'functional programming is great';
var result = string
.split(' ')
.map(v => v.slice(0, 1).toUpperCase() + v.slice(1))
.join(' ');
整個過程就是 join(map(split(str)))
,體現了函數式編程的核心思想: 通過函數對數據進行轉換。
函數式編程有兩個基本特點:
- 通過函數來對數據進行轉換
- 通過串聯多個函數來求結果
命令式舉例
我們通過編寫一條又一條指令去讓計算機執行一些動作,這其中一般都會涉及到很多繁雜的細節。命令式代碼中頻繁使用語句,來完成某個行爲。比如 for、if、switch、throw 等這些語句。
var CEOs = [];
for(var i = 0; i < companies.length; i++){
CEOs.push(companies[i].CEO)
}
函數式的特性
1、無副作用
var a = 1;
// 含有副作用,它修改了外部變量 a
// 多次調用結果不一樣
function test1() {
a++
return a;
}
// 無副作用,沒有修改外部狀態
// 多次調用結果一樣
function test2(a) {
return a + 1;
}
2、透明引用
指一個函數只會用到傳遞給它的變量以及自己內部創建的變量,不會使用到其他變量。
var a = 1;
var b = 2;
// 函數內部使用的變量並不屬於它的作用域
function test1() {
return a + b;
}
// 函數內部使用的變量是顯式傳遞進去的
function test2(a, b) {
return a + b;
}
3、不可變變量
指的是一個變量一旦創建後,就不能再進行修改,任何修改都會生成一個新的變量。使用不可變變量最大的好處是線程安全。多個線程可以同時訪問同一個不可變變量,讓並行變得更容易實現。 由於 JavaScript 原生不支持不可變變量,需要通過第三方庫來實現。 (如 Immutable.js,Mori 等等)
var obj = Immutable({ a: 1 });
var obj2 = obj.set('a', 2);
console.log(obj); // Immutable({ a: 1 })
console.log(obj2); // Immutable({ a: 2 })
4、函數是一等公民
我們常說函數是JavaScript的"第一等公民",指的是函數與其他數據類型一樣,處於平等地位,可以賦值給其他變量,也可以作爲參數,傳入另一個函數,或者作爲別的函數的返回值。下文將要介紹的閉包、高階函數、函數柯里化和函數組合都是圍繞這一特性的應用
常見的函數式編程模型
1、閉包
// 簡單的緩存工具
// 匿名函數創造了一個閉包
const cache = (function() {
const store = {};
return {
get(key) {
return store[key];
},
set(key, val) {
store[key] = val;
}
}
}());
console.log(cache) //{get: ƒ, set: ƒ}
cache.set('a', 1);
cache.get('a'); // 1
2、高階函數
高階函數指的是一個函數以函數爲參數,或以函數爲返回值,或者既以函數爲參數又以函數爲返回值。
JavaScript 語言是原生支持高階函數的, 例如Array.prototype.map,Array.prototype.filter 和 Array.prototype.reduce 是JavaScript中內置的一些高階函數,使用高階函數會讓我們的代碼更清晰簡潔。
3、函數柯里化
柯里化又稱部分求值,柯里化函數會接收一些參數,然後不會立即求值,而是繼續返回一個新函數,將傳入的參數通過閉包的形式保存,等到被真正求值的時候,再一次性把所有傳入的參數進行求值。
// 普通函數
function add(x,y){
return x + y;
}
add(1,2); // 3
// 函數柯里化
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
increment(2);// 3
4、函數組合
//兩個函數的組合
var compose = function(f, g) {
return function(x) {
return f(g(x));
};
};
//或者
var compose = (f, g) => (x => f(g(x)));
var add1 = x => x + 1;
var mul5 = x => x * 5;
compose(mul5, add1)(2);// =>15
redux 源碼上的 compose 函數:
// compose(f, g, h)(...args) => f(g(h(...args)))
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}