《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~ 本次就到这里,期待下次再会 ~

我是掘金安东尼,公众号【掘金安东尼】,输入暴露输入,技术洞见生活!

相关文章
相关标签/搜索