《XDM,JS如何函數式編程?看這就夠了!(五)》

本篇是《JS如何函數式編程》系列第五篇!共七篇,彷佛已經能望見勝利的彼岸了!!!web

紀伯倫曾說過:咱們都已經走的過久了,以致於忘了爲何出發。編程

We already walked too far, down to we had forgotten why embarked.設計模式

IPO5zG.md.png

因此,第五篇開始前,我們不如先來一個對前面每篇的梳理:數組

前文梳理

第一篇

《XDM,JS如何函數式編程?看這就夠了!(一)》,做爲「綱要篇」,重點解釋了:性能優化

  1. 本系列是基於《medium 五萬贊好文-《我永遠不懂 JS 閉包》》《「類」設計模式和「原型」設計模式——「複製」和「委託」的差別》兩篇的延伸探索,推薦閱讀。markdown

  2. 爲何要進行函數式編程?—— 一切只是爲了代碼更加可讀!!閉包

  3. 開發人員喜歡【顯式】輸入輸出而不是【隱式】輸入輸出,要明白何爲顯式,何爲隱式!!app

  4. 一個函數若是能夠接受或返回一個甚至多個函數,它被叫作高階函數。閉包是最強大的高階函數!!ide

第二篇

《XDM,JS如何函數式編程?看這就夠了!(二)》,講了重要的兩個概念:偏函數柯里化函數式編程

  1. 函數組裝是函數式編程最重要的實現方式!而熟練運用偏函數、柯里化,以及它們的變體,是函數組裝的基礎。

  2. 偏函數表現形式:partial(sum,1,2)(3)

  3. 柯里化表現形式:sum(1)(2)(3)

第三篇

《XDM,JS如何函數式編程?看這就夠了!(三)》,來到了「函數組裝」這一重點:

  1. 再次重申,函數組裝是函數式編程最重要的實現方式!!

  2. 函數組裝符合 「聲明式編程風格」,即聲明的時候你就知道了它「是什麼」!而不用知道它具體「幹了什麼」(命令式函數風格)!

  3. 好比:當你看到組裝後的函數調用是這樣,compose( skipShortWords, unique, words )( text ),就知道了它是先將 text 變成 words,而後 unique 去重,而後過濾較短長度的 words。很是清晰!

  4. compose(..) 函數和 partial(..) 函數結合,能夠實現豐富多彩的組裝形式!

  5. 封裝抽象成函數是一門技術活!不能不夠,也不宜太過!

第四篇

《XDM,JS如何函數式編程?看這就夠了!(四)》,咱們再細扣了下 「反作用」

  1. 開發人員喜歡顯式輸入輸出而不是隱式輸入輸出,學函數式編程,這句話要深刻骨髓的記憶!

  2. 解決反作用的方法有:定義常量、明確 I/O、明確依賴、運用冪等,記得對冪等留個心眼!

  3. 咱們喜歡沒有反作用的函數,即純函數!!

  4. 假如一棵樹在森林裏倒下而沒有人在附近聽見,它有沒有發出聲音?——對於這個問題的理解就是:假如你封裝了一個高級函數,在內部即便有反作用的狀況下,外界會知道這個信息嗎,它還算是純函數嗎?

以上即是咱們的簡要回顧!

咱們可能還須要更多時間去實踐和體會:

  1. 偏函數 partial(..) 和函數組裝 compose(..) 的變體及應用;
  2. 抽象的能力;
  3. 封裝高級的純函數;

OK!溫故知新,yyds!

第五篇,我們將基於實踐,分享最最多見的現象 —— 數組操做,看看它是如體現函數式編程精神!

數組三劍客

這三劍客是:map(..)filter(..)reduce(..)

map

咱們都會用 ES6 map(..) , 它「是什麼」,咱們很是清楚!

IPh9Cy.md.png

輕鬆寫一個 map(..) 的使用:

[1,2,3].map(item => item + 1)
複製代碼

可是,map(..) 「幹了什麼」,即它的內部是怎樣的,你知道嗎?

咱們能夠用原生實現一個函數 map(..)

function map(mapperFn,arr) {
    var newList = [];

    for (let idx = 0; idx < arr.length; idx++) {
        newList.push(
            mapperFn( arr[idx], idx, arr )
        );
    }

    return newList;
}

map(item=>item+1,[1,2,3])
複製代碼

咱們把一個 mapperFn(..) 封裝進模擬的 map(..) 函數內,其內部也是 for 循環遍歷。

咱們還能夠用 map(..) 作更多:

好比先將函數放在列表中,而後組合列表中的每個函數,最後執行它們,像這樣:

var increment = v => ++v;
var decrement = v => --v;
var square = v => v * v;

var double = v => v * 2;

[increment,decrement,square]
.map( fn => compose( fn, double ) )
.map( fn => fn( 3 ) );
// [7,5,36]
複製代碼

細細品一品~

filter

若是說map(..)的本質是映射值,filter(..)的本質是過濾值。如圖示意:

IPjRKX.md.png

[1,2,3].filter(item => item>2)
複製代碼

手寫一個 filter(..) 函數:

function filter(predicateFn,arr) {
    var newList = [];

    for (let idx = 0; idx < arr.length; idx++) {
        if (predicateFn( arr[idx], idx, arr )) {
            newList.push( arr[idx] );
        }
    }

    return newList;
}

filter(item=>item>2,[1,2,3])
複製代碼

一樣也是將一個函數做爲入參,處理一樣傳入的 arr,遍歷過濾獲得目標數組;

reduce

map(..)filter(..) 都會產生新的數組,而第三種操做(reduce(..))則是典型地將列表中的值合併(或減小)到單個值(非列表)。

IPjYsk.md.png

[5,10,15].reduce( (product,v) => product * v, 3 );
複製代碼

過程:

  1. 3 * 5 = 15
  2. 15 * 10 = 150
  3. 150 * 15 = 2250

手動實現 reduce 函數相較前兩個,要稍微複雜些:

function reduce(reducerFn,initialValue,arr) {
    var acc, startIdx;

    if (arguments.length == 3) {
        acc = initialValue;
        startIdx = 0;
    }
    else if (arr.length > 0) {
        acc = arr[0];
        startIdx = 1;
    }
    else {
        throw new Error( "Must provide at least one value." );
    }

    for (let idx = startIdx; idx < arr.length; idx++) {
        acc = reducerFn( acc, arr[idx], idx, arr );
    }

    return acc;
}
複製代碼

不像 map(..)filter(..) ,對傳入數組的次序沒有要求。reduce(..) 明確要採用從左到右的處理方式。

高級操做

基於 map(..)filter(..)reduce(..),咱們再看些更復雜的操做;

去重

實現:

var unique =
    arr =>
        arr.filter(
            (v,idx) =>
                arr.indexOf( v ) == idx
        );

unique( [1,4,7,1,3,1,7,9,2,6,4,0,5,3] );        
複製代碼

原理是,當從左往右篩選元素時,列表項的 idx 位置和 indexOf(..) 找到的位置相等時,代表該列表項第一次出現,在這種狀況下,將列表項加入到新數組中。

固然,去重方式有不少,可是,這種方式的優勢是,它們使用了內建的列表操做,它們能更方便的和其餘列表操做鏈式/組合調用。

這裏也寫一下reduce(..) 實現:

var unique =
    arr =>
        arr.reduce(
            (list,v) =>
                list.indexOf( v ) == -1 ?
                    ( list.push( v ), list ) : list
        , [] );
複製代碼

降維

二位數組轉一維數組

[ [1, 2, 3], 4, 5, [6, [7, 8]] ] => [ 1, 2, 3, 4, 5, 6, 7, 8 ]
複製代碼

實現:

var flatten =
    arr =>
        arr.reduce(
            (list,v) =>
                list.concat( Array.isArray( v ) ? flatten( v ) : v )
        , [] );
複製代碼

你還能夠加一個參數 depth 來指定降維的層數:

var flatten =
    (arr,depth = Infinity) =>
        arr.reduce(
            (list,v) =>
                list.concat(
                    depth > 0 ?
                        (depth > 1 && Array.isArray( v ) ?
                            flatten( v, depth - 1 ) :
                            v
                        ) :
                        [v]
                )
        , [] );

flatten( [[0,1],2,3,[4,[5,6,7],[8,[9,[10,[11,12],13]]]]], 2 );
// [0,1,2,3,4,5,6,7,8,[9,[10,[11,12],13]]]
複製代碼

看到這裏,若是以爲複雜,你能夠只把它做爲一個庫來調用便可。實際上,咱們後續還會專門來介紹各種函數式編程函數庫

融合

仔細體會下,如下給出的三段代碼,哪段你以爲你更容易看懂?哪一段更符合函數式編程?

// 實現 1
[1,2,3,4,5]
.filter( isOdd )
.map( double )
.reduce( sum, 0 );                    // 18

// 實現 2
reduce(
    map(
        filter( [1,2,3,4,5], isOdd ),
        double
    ),
    sum,
    0
);                                    // 18

// 實現 3
compose(
    partialRight( reduce, sum, 0 ),
    partialRight( map, double ),
    partialRight( filter, isOdd )
)
( [1,2,3,4,5] );                     // 18

複製代碼

在片斷 1 和 片斷 3 中沒法抉擇?

再看一例:

var removeInvalidChars = str => str.replace( /[^\w]*/g, "" );

var upper = str => str.toUpperCase();

var elide = str =>
    str.length > 10 ?
        str.substr( 0, 7 ) + "..." :
        str;

var words = "Mr. Jones isn't responsible for this disaster!"
    .split( /\s/ );

words;
// ["Mr.","Jones","isn't","responsible","for","this","disaster!"]

// 片斷 1
words
.map( removeInvalidChars )
.map( upper )
.map( elide );
// ["MR","JONES","ISNT","RESPONS...","FOR","THIS","DISASTER"]

// 片斷 3
words
.map(
    compose( elide, upper, removeInvalidChars )
);
// ["MR","JONES","ISNT","RESPONS...","FOR","THIS","DISASTER"]
複製代碼

重點就是:

咱們能夠將那三個獨立的相鄰的 map(..) 調用步驟當作一個轉換組合。由於它們都是一元函數,而且每個返回值都是下一個點輸入值。咱們能夠採用 compose(..) 執行映射功能,並將這個組合函數傳入到單個 map(..) 中調用:

因此:片斷 3 這種融合的技術,是常見的性能優化方式。

階段小結

以上,咱們看到了:

三個強大通用的列表操做:

  1. map(..): 轉換列表項的值到新列表;
  2. filter(..): 選擇或過濾掉列表項的值到新數組;
  3. reduce(..): 合併列表中的值,而且產生一個其餘的值(也多是非列表的值);

這是咱們日常用的最多的數組遍歷方式,但此次咱們藉助函數式編程思想把它們升級了!

這些高級操做:unique(..)、flatten(..)、map 融合的思想等(其實還有不少其它高級操做),值得咱們去研究、感覺體會,最後運用到實踐中去!!

OK~ 本次就到這裏,期待下次再會 ~

我是掘金安東尼,公衆號【掘金安東尼】,輸入暴露輸入,技術洞見生活!

相關文章
相關標籤/搜索