JavaScript 函數式編程 -- 劃重點了!!!

函數是一等公民

在談到函數式編程的時候,不少時候會聽到這樣一句話 "函數是一等公民"。那咱們如何去理解這句話呢? 編程

"一等" 這個術語一般用來描述值。因此當咱們說 "函數是一等公民" 時,也就是說函數擁有值的一切特性,你能夠像看待一個值同樣來看待一個函數。舉個例子,數字在 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 兩個新的函數。可以生成閉包的函數,其實都是高階函數。

到這裏,第一部分重點內容就講完了。在下一部分中,咱們會講到函數式編程中剩下的幾個重要部分:

  • 柯里化和組合
  • 部分應用
  • 遞歸
  • 基於流的編程
相關文章
相關標籤/搜索