范式篇 - 如何把函数式编程合理运用到日常工作中

7,418 阅读8分钟

前言

相信很多做前端的朋友,对函数式编程或多或少都有听说过,对函数式编程的概念大家也可能都听说过了,本篇文章前面会讲一些有关函数式编程的特性和优点,但是不会占用太多的内容,毕竟讲优点和特性的文章网上一找一大堆

这篇文章最主要的事情,就是帮助不了解的函数式编程的朋友,了解函数式编程;了解函数式编程的朋友,不知道如何运用,帮你分析运用到实际的项目当中

特性

函数是 “第一等公民”

从字面的意思就可以理解,函数在函数式编程当中,是不可或缺的一部分。

闭包和高阶函数

闭包和高阶函数的概念网上也是一搜一大堆,这里不做讲解,百度一下,你就知道。

惰性计算

这个是函数式编程当中,很常用,也是非常需要了解的一部分,函数式编程很多时候,都是需要做惰性计算的,举个网上常见的🌰:

// 我要做2+3、2+4的操作

// 传统写法
var a = 2;
var b = 3;
var c = 4;
a + b || a + c

// 函数式写法
var calc = a => b => a + b
var addA = calc(2)
addA(3) || addA(4)

一句话概括一下: 在做不同的事情的情况下,可能是基于某一个相同的条件

没有副作用

和惰性计算一样,这也是函数式编程一个非常重要的一个特点,使用函数式编程去写的代码,是不在乎外面变量环境的改变,它在乎的只有传参,而且他是不会针对任何外部的变量去做修改,最后它 return 回来的结果,只是针对传参的一个计算,在举个🌰:

// 非函数式
var a = 1;
function calc(){
    ++a
}
calc()

// 函数式
var a = 1;
function calc(num){
    return ++num
}
calc(a)

一句话概括一下:不要让你的函数,去影响到外部的变量

不修改状态

其实没有副作用的主要要表达的意思,就是不修改状态,因为你的函数只做你自己要做的事情

一句话概括一下:百因必有果,你给我传值,我就给你返回值

总结

函数式编程还有几个 js 的特性会经常用到,比如函数的 作用域作用域链this指针 、还有上面提到的 闭包高阶函数,这都是在函数式编程当中,非常需要了解的点

这里最主要是给之前没有接触过,或者不了解函数式编程的朋友,简单讲一下什么是函数式编程,讲的很糙,但是核心的意思大概已经表达了,有不了解的可以留言,缘分回复;如果讲的不对的地方可以留言指出

案例分析

案例一:属性计算 - 针对属性的格式化处理

在日常的工作当中,后端给我们返回的结果,不一定是我们要的,比如这个时候,我们要做一个 a_bc 转换成驼峰 aBc 的变化,如果少的话,好说,一个 if 就搞定,如果存在很多个,不确定的情况,但是我们也要做类似的转换怎么办呢

// 非函数式写法
if (str === 'a_bc') {
    str = 'a_Bc'
}

// 函数式写法
function strTransform(str) {
    return str => str.replace(/_[a-z]{1}/g, m => m.replace('_', '').toLocaleUpperCase())
}

这样使用起来,就很方便,而且随时都能用,可能很多朋友会说,这不是经常会使用到的方法吗,这算函数式吗?

算不算得根据情况,如果是你直接修改了传进来的参数,那不算;如果你是通过传进来的参数,做了计算后,返回了新的值,那这算,因为你不会修改原数据,这样也就没有了副作用

案例二:功能组件 - 选项卡切换

选项卡,使我们日常工作当中,经常会用到功能,针对选项卡,我们要做一个 “纯” 组件:

非函数式编程的写法

<!-- vue -->
<template>
    <div v-for="(item, index) in arr" @click="switchTab($event, index)">
        {{item.name}}
    </div>
</template>
<script>
export {
    data: {
        arr:[]
    },
    mounted: fcuntion() {
        this.arr = ajaxResult;
        // ajaxResult 代表的是接口返回的结果
    },
    methods: {
        switchTab (e, index) {
            // 针对当前点击的处理
        }
    }
}
</script>
// react
class Tab extends Components{
    constructor () {
        super()
        this.arr = []
        this.switchTab.bind(this)
    },
    componentWillMount () {
        this.arr = ajaxResult
    },
    switchTab(index) {
        // 针对当前点击的处理
    },
    render () {
        var list = this.arr.map((item, index) => <div onClick={e => this.switchTab(e, index)}>{item.name}</div>)
        return (
            <div>
                {list}
            </div>
        )
    }
}

react 很久没有写了,这可能是一份伪代码,这两个代码要表述的意思,就是一个很普通的选项卡,为了让对函数式基本没什么了解的朋友能快速了解我的意思,所以我写了两个框架的代码

函数式编程的写法

<!-- vue -->
<template>
    <div v-for="(item, index) in arr" @click="callback($event, index)">
        {{item.name}}
    </div>
</template>
<script>
export {
    props: ['arr', 'callback']
}
</script>
// react
function tab ({arr, callback}) {
    return (
        <div>
            {arr.map((item, index) => <div onClick={e => callback(e, index)>{item.name}</div>)}
        </div>
    )
}

大家会发现使用函数式编程代码量写法会少很多,其实呢,真正的情况下,代码量并不会少,可能还会多一些,但不会多太多,但是这样写很符合函数式编程的思想:“纯” 、 没有副作用、不修改状态

组件的内部,只会做两件事,渲染和执行点击对应按钮的回调,所以说,在哪里用,都不重要,只要符合该组件的数据格式,就可以正常展示

案例三:跨组件合作 - 抛物线

类似这样的功能,这里涉及的代码比较复杂,就不写了,多写点理论吧

首先呢,我们要确认几个这种需求的特点:

  • 每个功能都要拆分不同的组件
  • 购物车展示功能不确定,但是只要是添加物品,就需要抛到购物车内
  • 由于不同的购物车展示,导致抛物线的角度不同
  • 功能之间是跨组件的,我们不可能只通过一个 class 或者一个 id 去判断我要抛到的位置,也不能通过一个 class 或者一个 id 来确定我是否要从哪里开始抛

这是三个很基本的需求,这样呢,我们就要使用到函数式编程的几个特性:函数、闭包、高阶函数、惰性计算、没有副作用

  • 首先,写一个函数,接收的是一个入口,就是从哪里开始抛这个点
  • 接收到要抛的位置,我们就可以去创建一个抛物线的点出来,但是不会 appendChild 到页面当中,只是 create
  • 接着用一个函数定义你抛物线的方向,这里可以你用抛物线公式去算、也可以是用 css3animation,我建议使用贝塞尔曲线
  • 最后设置一个函数,接收的是你要抛到的位置

这样的一个抛物线功能,是一个完全独立的,没有任何依赖性的,只做自己抛物线功能,不考虑是谁让我抛的

问答

问:函数式编程频繁调用函数好不好

:不能用好不好来形容,要用是否适合当前的业务场景来形容,针对某些情况下,函数式编程没有副作用、而且还干净、重点不会影响原数据,保证在互相合作的过程当中,不会因为自己的功能而影响到他人

问:函数式编程好还是命令式编程好

:和上面的回答一样,没有最好,只有最合适,大家从一开始到中级左右的时候,都会产生一个误区,就是面向对象的写法很好,很牛逼,比面向过程要好用

其实呢,这是一个很严重的误区,当大家在往上走得时候,就会发现,其实好坏都是针对场景,没有谁敢说什么是最好的,假如我们要做一个专题页,这时候使用 react 或者 vue 其实是最烂的方法,或者说什么我封装一个选项卡,我写的怎么怎么好,没那个必要

专题页的定位,就是一个快速展示给用户的页面,我们要的就是快点展示出来,如果你使用框架,还要经历框架的初始化,加载框架的资源,你封装的非常厉害的组件的初始化逻辑,其实这个时候用面向过程,一把梭到底可能是更好的方案

当然了,有人会说,现在网络速度这么快,那些时间可以忽略不计,但是你不能说有的人确实环境没有我们的环境这么好,我们公司还有使用 ios8 系统的用户呢

总结

我可能不如网上的一些文章,针对函数式编程的理论讲的那么清楚,因为本来这也不是一篇针对函数式编程概念的讲解,我是想帮助在不知道函数式编程,或者知道不会用的朋友,真正的做个引路人,让大家把函数式编程真正的运用到项目当中

反正如果大家真的想把函数式编程运用到项目当中,只要记住函数式编程的特点,合理的运用,不要强行的写成函数式,就好像问答部分一样,大家要找到合适的,而不是必须用什么

每一种好的模式,都有其存在的意义,我们要去尽量的了解每一种模式为什么要这么做,这么做的优势在哪里

结束语

感谢大家能一口气看到这里,也许是跳着看的,这都不是重点,只要看了的朋友了解了,发现自己的业务场景当中,确实有场景可以使用,那说明我这篇文章写的就有价值了

如果有写的不准确的地方,或者有需要调整的地方,请大家及时指出,欢迎每一个大佬的指点,你们指出我的不足,就是我成长的一步,谢谢

最后和大家说一下,我也整了个公众号,内容会和掘金同步,当然了,肯定是先发掘金的,大家感兴趣的可以关注一下我的公众号