帶你入門函數式編程

帶你入門函數式編程

1、什麼是函數式編程

1.概念

首先,函數式編程是一種編程範型。其餘的編程範型有面向過程編程(關注計算機的執行步驟,指定先作這個再作那個),還有面向對象(建立對象而後對象又有方法而後又能夠改變它們)等等。函數式編程也是編程範型,函數爲王。**javascript

這裏須要介紹下編程範型的概念,編程範型/編程範式(範即模範、典範之意,範型即模式、方法)是一類典型的編程風格,是指從事軟件工程的一類典型的風格。如函數式編程、過程式編程、面向對象編程、指令式編程等等爲不一樣的編程範型。
編程範型提供了(同時決定了)程序員對程序執行的見解。例如,在面向對象編程中,程序員認爲程序是一系列相互做用的對象,而在函數式編程中一個程序會被看做是一個無狀態的函數計算的序列。html

函數式編程也是一種編程風格,完成項目時怎樣組織編寫代碼。
函數式編程更是一種觀念,能夠以一種思考問題的方式來完成任務,也是一種趨勢前端

2.優勢

  • 更安全,更容易調試,構建項目時更好去維護:函數式編程不依賴、也不會改變外界的狀態,只要給定輸入參數,返回的結果一定相同。所以,每個函數均可以被看作獨立單元,頗有利於進行單元測試和除錯,以及模塊化組合。
  • 有龐大的使用函數式編程開發JS的開發者社區
  • 代碼簡潔,抽象程度高,容易複用,開發快速:函數式編程大量使用函數,減小了代碼的重複,所以程序比較短,開發速度較快,抽象程度高,也更容易複用。
  • 接近天然語言,易於理解:函數式編程的自由度很高,能夠寫出很接近天然語言的代碼。如如下代碼:(merge([1,2],[3,4]).sort().search("2"),應該一眼就能明白它的意思。
  • 易於"併發編程":函數式編程不須要考慮"死鎖",由於它不修改變量,因此根本不存在"鎖"線程的問題。沒必要擔憂一個線程的數據,被另外一個線程修改,因此能夠很放心地把工做分攤到多個線程,部署"併發編程"。

3.使用指南

(1)在函數式編程中,一切都要函數化

要用函數實現程序,給定輸入,會有一個輸出,咱們更多的要考慮數據的輸入輸出流,須要想怎樣用函數表達一切。java

  • 非函數化:(命令式風格)
const name = "ming";
const greeting = "hello,";
console.log(greeting + name); // hello,ming
  • 函數化:
function greet(name) {
	return `hello,${name}`;
}
greet("ming"); // hello,ming

(2)使用純函數,避免反作用

反作用:反作用是指在計算過程當中,系統狀態的一種變化,或者是與外部進行的可觀察的交互。
反作用可能包含,但不限於:git

  • 更改文件系統
  • 往數據庫插入記錄
  • 發送一個 http 請求
  • 可變數據
  • 打印/log
  • 獲取用戶輸入
  • DOM 查詢
  • 訪問系統狀態

函數式編程的哲學就是假定反作用是形成不正當行爲的主要緣由。
固然這並非說,要禁止使用一切反作用,而是說,要讓它們在可控的範圍內發生。程序員

純函數:針對相同的一組輸入,會永遠獲得相同的輸出,並且沒有任何可觀察的反作用,這樣的函數叫純函數。
用一個例子來講明下。數組裏的slice和splice方法,這兩個函數的做用是同樣的,可是須要注意的是,splice會改變原數組,而slice不會改變原數組。因此slice每次用相同的輸入去執行都會獲得相同的輸出,符合純函數的定義。而splice改變了原數組,每次用相同的輸入去執行都會獲得不一樣的輸出,產生了反作用。github

let arr = [1,2,3,4,5];

// 純的
arr.slice(0,3); // [1,2,3]
arr.slice(0,3); // [1,2,3]
arr.slice(0,3); // [1,2,3]

// 不純的
arr.splice(0,3); // [1,2,3]
arr.splice(0,3); // [4,5]
arr.splice(0,3); // []

再看另一個例子:數據庫

// 不純的
var b = 21;

var test = function(a) {
  return a >= b;
};


// 純的
var test = function(a) {
  var b = 21;
  return a >= b;
};

在不純的版本中,函數外部的變量會影響到函數的返回結果,或者說它引入了外部的環境。
而在純的版本中,函數就能夠本身保持「獨立」。編程

(3)使用高階函數:函數能夠做爲輸入/輸出

function makeAdjectifier(adjective) {
    return function (string) {
        adjective + " " + string;
    }; 
}
var coolifier = makeAdjectifier("cool");
coolifer("conference");  // => "cool conference"

不要使用for/while等迭代,使用map,reduce,filter這樣的高階函數,能夠做爲一個函數去應用。數組

(4)避免可變性:使用不可變數據

很差的作法:

let arr = ['1','2','3'];
arr[2] = '4';
console.log(arr); // ['1,'2','4']

好的作法:

const arr = ['1','2','3'];
const newArr = arr.map(item => {
	if(item === '3') {
  	return '4';
  }
  return item;
});
console.log(arr); // ['1,'2','3']
console.log(newArr); // ['1,'2','4']

(5)持久的數據結構,實現高效的不變性

當咱們將事物視爲不變時,咱們最終所作的事情就是爲全部事物製做新的副本。
若是有一個數組[1,2,3],咱們要改變3爲4,那麼在可變的環境中直接把3換爲4便可,可是以前說過要避免這種操做,就要取而代之,複製一個新數組,複製1,2而後輸入4,就須要花更多時間存儲兩個數組,這是很是可怕的。咱們能夠把這個數組當作一棵樹,樹上的葉子結點就是要存儲東西的地方,若是想改變某個數據,只須要製造一個新節點存4,能夠新建一個有1,2,4的樹,能夠服用以前已經存在的數據。這個叫作數據共享。這樣咱們能夠不用浪費不少時間和存儲空間去更新數據。

2、柯里化

概念:只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數。

var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);
var addTen = add(10);

increment(2);
// 3

addTen(2);
// 12

上面咱們定義了一個add函數,它接收一個參數,並返回一個新的函數。調用add後,返回的函數就以閉包的方式記住了它的第一個參數x。

3、組合

組合其實就是你能夠選擇兩個函數,讓它們結合,產生一個新的函數。這樣寫可使咱們的代碼更加優雅。

var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
};

f 和 g 都是函數,x 是在它們之間經過「管道」傳輸的值。

var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);

shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"

參考:
Learning Functional Programming with JavaScript, by Anjana Vakil — JSUnconf 2016
函數式編程初探
編程範型wiki
函數式編程指北中文版

更多文章以及分享請關注微信公衆號 前端er的分享,不止於前端,按期輸出一些技術知識、生活感想、理財知識等。

相關文章
相關標籤/搜索