初识函数式编程 | 函数式编程

1,712 阅读6分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

hi~ 我是 无言非影, 很高兴在这里分享我的学习历程。

关于 函数式编程 整理的一些笔记,都统一记录在: 函数式编程专栏


函数式编程有哪些好处?

  • 函数式编程可以抛弃this
  • 打包过程中可以更好的利用 three-sharking 过滤无用代码
    • tree-shaking来自于rollup.js,原理相同,就是通过分析静态的ES模块,来剔除未使用代码的。
  • 方便测试、方便并行处理
  • 可以借助很多库帮助函数式开发:lodashunderscoreramda
  • 函数式编程是随着 React 的流行受到越来越多的关注。 Vue3也开始拥抱函数式编程。

常见的编程范式

  • 过程化编程 / 命令式编程
    • 过程化语言适合解决线性的算法问题,强调“自上而下”、“精益求精”的设计方式。
  • 事件驱动编程
    • 事件驱动常常用于用户与程序的交互,通过图形用户接口(鼠标、键盘、触摸板)进行交互式的互动。当然,也可以用于异常的处理和响应用户自定义的事件等等。
  • 面向对象 OOP
    • 面向对象的程序设计包括三个基本概念:封装性、继承性、多态性。面向对象的语言通过类、方法、对象和消息传递,来支持面向对象的程序设计范式。
  • 函数式编程
    • 函数式编程即是在软件开发的工程中避免使用共享状态(Shared State)、可变状态(Mutable Data)以及副作用(Side Effects)。

JS常用的两种编程范式

  • 原型继承(OOP) :原型链
  • 函数式编程:闭包(closure)、头等函数(First-class Function)

函数式编程的定义

函数式编程(Functional Programming),缩写FP,是一种编程范式,也是一种编程风格,和面向对象是并列关系。 可以认为它是一种思维模式+实现方法。 思维方式就是把现实世界事物和事物之间的联系抽象到程序世界,是对运算过程的一种抽象

  • 函数式编程的核心是 只使用纯粹的数学函数编程函数的结果仅取决于参数,而没有副作用,就像I/O或者状态转换这样。
    • I/O(英语:Input/Output),即输入/输出,通常指数据在存储器(内部和外部)或其他周边设备之间的输入和输出,是信息处理系统(例如计算机)与外部世界(可能是人类或另一信息处理系统)之间的通信。输入是系统接收的信号或数据,输出则是从其发送的信号或数据。

函数式编程和面向对象编程的不同

面向对象编程

  • 优点:命令式的编码风格、代码读起来容易,像是一组直接的、计算机很容易就能遵循的指令。
  • 不足:面向对象编程往往需要共享状态,容易造成竞争条件( Race condition)漏洞(例如原型链上面,几个函数在调用同一批资源时可能产生的bug )。

函数式编程

  • 优点:不需要担心共享状态或者副作用、把代码组合成复用性更强的代码、“无参风格”(point-free style,也叫隐式编程)、利用纯函数,不用担心线程资源冲突、竞争条件之类的问题。
  • 不足:代码如果过度利用了函数式的编程特性(如无参风格、大量方法的组合),就会影响其可读性,从而简洁度有余、易读性不足。

函数式编程思维

  • 程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多输入和输出的函数。
  • 函数式编程中的函数指的不是程序中的函数Function,而是数学中的函数即映射关系,例如:y=sin(x),是这种x和y的关系
  • 相同的输入时中要得到相同的输出(纯函数)
  • 函数式编程用描述数据(函数)之间的映射
// 传统的过程式编程,可能这样写:
  let a = 1 + 2;
  let b = a * 3;
  let c = b - 4;
// 函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:
  let result = subtract(multiply(add(1,2), 3), 4);

函数式编程的五大特点

  • 函数是"第一等公民"
    • 在JS中函数就是一个普通的对象,我们可以把函数存储到变量/数组中,它还可以作为另一个函数的参数和返回值, 也可以通过 new Function()构建一个构造函数
  • 只用"表达式",不用"语句"
    • 表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。
    • 函数式编程表达式"(expression),每一步都是单纯的运算,而且都有返回值。
  • 没有"副作用"
    • 所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
    • 函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
      let list = ["a", "b"] 
      // 纯函数 - 没有副作用, 对于相同的函数,输出是一样的
      
      // slice方法,截取的时候返回截取的函数,不影响原数组
      list.slice(0, 2) // =>  ["a", "b"]
      list.slice(0, 2) // => ["a", "b"]
      
      // 不纯的函数 - 有副作用,对于相同的输入,输出是不一样的
      
      // splice方法,返回原数组,改变原数组
      list.splice(0, 2) // =>  ["a", "b"] 
      list.splice(0, 2) // => []
      
  • 不修改状态
    • 函数式编程只是返回新的值,不修改系统变量
    • 函数式编程使用参数保存状态
    无状态其实就是不做数据持久化(写入数据库或缓存),一个功能模块的输入和输出应仅由函数的参数和返回值定义。因为如果你在这个函数里对数据做了持久化,那就相当于对系统做了个隐藏的输出,从函数定义上是看不出来的,无异于增加编码人员的心智负担,同时也可能出现各种数据并发引起的异常情况。
    
  • 引用透明
    • 引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。

附录