在談到函數式編程的時候,不少時候會聽到這樣一句話 "函數是一等公民"。那咱們如何去理解這句話呢? 編程
"一等" 這個術語一般用來描述值。因此當咱們說 "函數是一等公民" 時,也就是說函數擁有值的一切特性,你能夠像看待一個值同樣來看待一個函數。舉個例子,數字在 JavaScript 中是一等公民,那麼數字擁有的特性,也一樣被函數所擁有。數組
const num = 10; const fun = function() { return 10; }
const a = [10, function() { return 20; } ]
const b = { name: 'Tony', age: function() { return 20; } }
10 + (function() { return 20; })(); // 30
const well = function() { return 10; } const good = function() { return function() { return 10; }; }
const fun = function(value) { return value; } const happy = function(func) { return func(5) * 10; } happy(fun); // 50
最後兩條其實就是 高階函數
的定義,若是你如今不理解也沒有關係,咱們在後面的部分會講到它。閉包
變量的做用域和閉包做爲 JavaScript 的基礎,在學習函數式編程中是很是重要的,只有理解了它們,你才能更好的去理解咱們後面要講到的高階函數和部分應用等。app
關於變量的做用域,你須要知道:函數式編程
全局做用域
: JavaScript 中擁有最長生命週期 (一個變量的多長的時間內保持必定的值) 的變量,其變量的生命週期將跨越整個程序。globalVariable = 'This is a global variable!';
詞法做用域
: 詞法做用域其實就是指一個變量的可見性,以及它文本表述的模擬值。a = 'outter a'; function good() { a = 'middle a'; return function() { a = 'inner a'; return 'I am ' + a; } } good(); // I am inner a
PS:這裏的示例代碼僅僅是爲了學習,你最好不要這樣去寫,由於它會讓你的代碼變得使人費解。
在上面的例子中,咱們分別對 a 變量進行了三次賦值,那麼爲何最後咱們拿到 a 的值是 'inner a' 而非其餘呢?函數
當咱們聲明 a = 'outter a'
時,程序會在棧中開闢一個空間去存儲 a,當執行 good()
函數時,咱們聲明瞭 a = 'middle a'
,這時候會將棧中 a 的值修改掉,變成 'middle a'
,最後在執行 return 語句時,咱們又聲明瞭 a = 'inner a'
,這時候會再次修改棧中的 a 的值,變成 'inner a'
。所以獲得了上面的結果。學習
在屢次給同一變量賦值時,最後獲得的值是離使用時最近的一次賦值。經過查找離使用時最近的一次賦值,咱們能夠快速的得出最後的結果。this
動態做用域
提到 JavaScript 的動態做用域,就不得不提到 this
了。this
相關的知識不少,以後有時間再詳細來說講。如今咱們先記住 this
所指向的值由調用者肯定,以下代碼所示:code
function globalThis() { return this; } globalThis.call('APPLE'); //=> 'APPLE' globalThis.call('ORANGE'); //=> 'ORANGE'
函數做用域
提及閉包,不少人都會以爲有點頭疼,這的確是一個使人費解的概念,不過不要怕,它其實沒有那麼難以理解。對象
閉包是一個函數和聲明該函數的詞法環境的組合
換句話說,閉包就是在使用時被做用域封閉的變量和函數。閉包能夠捕獲函數的參數和變量。
舉個例子:
const fun = function() { const a = 10; return function(b) { return a + b; } } const myFunc = fun(); // 此時 myFunc 就變成一個閉包了,這個閉包能夠捕獲 fun 函數裏的 a 變量,b 參數。
注意閉包是在使用時纔會生成的,而非建立時。如上面的例子,若是隻建立 fun 函數,而不執行最後一句 fun(),那麼 fun 並不能稱之爲一個閉包。這裏的閉包應該是 fun 運行時所產生的做用域,這個做用域捕獲了fun 裏面的變量和參數。
再來看一個例子:
const fun = function() { return function() { return 10; } } const myFunc = fun(); // myFunc 不是一個閉包 const fun2 = function(value) { return function() { return value; } } const myFunc2 = fun2('AWESOME'); // myFunc2 是一個閉包 myFunc2(); // AWESOME myFunc2(); // AWESOME 屢次執行 myFunc2 閉包,返回的值相同 const myFunc3 = fun2('HAPPY'); // myFunc3 是一個新的閉包 myFunc3(); // HAPPY
這裏 myFunc 嚴格意義上並不能叫做一個閉包,由於它並無捕獲 fun 任何的變量或者是函數的傳參。而 myFunc2 是一個閉包,由於它捕獲了 fun2 的傳參。
閉包延續了變量的生命週期,若是不手動銷燬,閉包裏面的變量會一直存在內存中。好比當咱們手動將 myFunc = null 時,閉包裏面的變量纔會被垃圾回收。
說了這麼多,你可能會有這樣的疑問,閉包真的有用嗎?閉包通常都會用到什麼地方?
*1. 用閉包模擬私有方法, 使公共函數可以訪問私有函數和變量,實現數據的隱藏和封裝。私有方法有利於限制對代碼的訪問,而且提供了強大的管理命名空間的能力,避免了非核心代碼對公共接口的干擾。
const Counter = () => { let count = 0; const change = (a) => { count = count + a; } return { increase: () => { change(1); }, decrease: () => { change(- 1); }, value: () => { return count; } } } const func1 = new Counter(); func1.value(); // 0 func1.increase(); func1.value(); // 1 func1.decrease(); func1.value(); // 0
*2. 經過一個高階函數,生成不一樣的閉包,從而獲得多個保存不一樣環境的新函數。
以下面的例子:
const makeAdder = function(x) { return function(y) { return x + y; } } const add5 = makeAdder(5); const add10 = makeAdder(10);
makeAdder 實際上是一個函數工廠,用於建立將制定的值和它的參數求和的函數。經過它咱們又建立了兩個新函數 add5 和 add10。add5 和 add10 都是閉包。它們共享着相同的函數定義,可是卻保存了不一樣的環境。在 add5 的環境中,x 爲 5,可是在 add10 中,x 則爲10。
知足如下任意條件之一便可稱之爲高階函數:
咱們常見的 map,find,reduce 都是以函數做爲入參的函數,因此它們都是高階函數。
使用函數做爲函數的參數,可讓咱們建立出更靈活的函數。經過將參數從值替換爲函數,咱們能夠獲得更多的可能性。由於在調用的時候,咱們能夠經過傳入不一樣的函數來完成不一樣的需求。
正以下面的例子:
const finder = function(val, func) { return val.reduce(function(prev, current) { return func(prev, current); }); } const a = [1, 2, 3, 5, 8]; finder(a, Math.max); // 8 finder(a, Math.min); // 1
在使用 finder 函數時,經過傳入不一樣的函數,最後獲得了徹底不一樣的結果。這也是爲何咱們強調 "使用函數,而不是值" 的緣由。
以函數做爲返回結果的函數,能夠構建強大的函數。還記得咱們前面提到的閉包嗎? 經過高階函數 makeAdder,咱們生成了 add5 和 add10 兩個新的函數。可以生成閉包的函數,其實都是高階函數。
到這裏,第一部分重點內容就講完了。在下一部分中,咱們會講到函數式編程中剩下的幾個重要部分: