FunctionalProgramming

:arrow_left::arrow_left:良好习惯

可扩展性--我是否需要不断地重构代码来支持额外的功能?
易模块化--如果我更改了一个文件,另一个文件是否会受到影响?
可重用性--是否有很多重复的代码?
可测性--给这些函数添加单元测试是否让我纠结?
易推理性--我写的代码是否非结构化严重并难以推理?

函数式编程有用吗

:top::top:FunctionalProgramming

什么是函数式编程

:top::top:FunctionalProgramming

函数式编程是一种强调以函数使用为主的软件开发风格

函数式编程的目的是使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变

现在的需求就是输出在网页上输出 “Hello World”。

document.querySelector('#msg').innerHTML = '<h1>Hello World</h1>'

这句代码没错,也很简单,但是所有代码都是死的,不能重用,如果想改变消息的格式、内容等就需要重写整个表达式,所以可能有经验的前端开发者会这么写。

function printMessage(elementId, format, message) {
 document.querySelector(elementId).innerHTML = `<${format}>${message}</${format}>`
}
printMessage('msg', 'h1', 'Hello World')

这样确实有所改进,但是任然不是一段可重用的代码,如果是要将文本写入文件,不是非 HTML,或者我想重复的显示 Hello World。
那么作为一个函数式开发者会怎么写这段代码呢?

//实现一个compose函数
function compose() {
 var fns = [].slice.call(arguments)
 return function (initialArg) {
 var res = initialArg
 for (var i = fns.length - 1; i > -1; i--) {
 res = fns[i](res)
 }
 return res
 }
}
const printMessage = compose(addToDom('msg'), h1, echo)
printMessage('Hello World')

compose 简单解释一下,他会让函数从最后一个参数顺序执行到第一个参数,compose 的每个参数都是函数,初始函数一定放到参数的最右面。

现在我们需要将文本重复三遍,打印到控制台。

var printMessaage = compose(console.log, repeat(3), echo)
printMessage(‘Hello World’)

声明式编程

函数式编程属于声明是编程范式:这种范式会描述一系列的操作,但并不会暴露它们是如何实现的或是数据流如何传过它们。

我们再来看一组代码再来对比一下命令式编程和声明式编程。

// 命令式方式
var array = [0, 1, 2, 3]
for(let i = 0; i < array.length; i++) {
 array[i] = Math.pow(array[i], 2)
}
array; // [0, 1, 4, 9]

// 声明式方式
[0, 1, 2, 3].map(num => Math.pow(num, 2))
  • 命令式很具体的告诉计算机如何执行某个任务。
  • 声明式是将程序的描述与求值分离开来。它关注如何用各种表达式来描述程序逻辑,而不一定要指明其控制流或状态关系的变化。

为什么我们要去掉代码循环呢?循环是一种重要的命令控制结构,但很难重用,并且很难插入其他操作中。而函数式编程旨在尽可能的提高代码的无状态性和不变性。要做到这一点,就要学会使用无副作用的函数--也称纯函数

纯函数

纯函数指没有副作用的函数。相同的输入有相同的输出,就跟我们上学的函数一样。

  • 常常这些情况会产生副作用
  • 改变一个全局的变量、属性或数据结构
  • 改变一个函数参数的原始值
  • 处理用户输入
  • 抛出一个异常
  • 屏幕打印或记录日志
  • 查询 HTML 文档,浏览器的 Cookie 或访问数据库
var counter = 0
function increment() {
 return ++counter;
}

这个函数就是不纯的,它读取了外部的变量,可能会觉得这段代码没有什么问题,但是我们要知道这种依赖外部变量来进行的计算,计算结果很难预测,你也有可能在其他地方修改了 counter 的值,导致你 increment 出来的值不是你预期的。

  • 对于纯函数有以下性质:
  • 仅取决于提供的输入,而不依赖于任何在函数求值或调用间隔时可能变化的隐藏状态和外部状态。
  • 不会造成超出作用域的变化,例如修改全局变量或引用传递的参数。

引用透明

引用透明是定义一个纯函数较为正确的方法。纯度在这个意义上表面一个函数的参数和返回值之间映射的纯的关系。如果一个函数对于相同的输入始终产生相同的结果,那么我们就说它是引用透明。

// 非引用透明
var counter = 0
function increment() {
 return ++counter
}

// 引用透明
var increment = (counter) => counter + 1

不可变性

不可变数据是指那些创建后不能更改的数据。与许多其他语言一样,JavaScript 里有一些基本类型(String,Number 等)从本质上是不可变的,但是对象就是在任意的地方可变。

var sortDesc = function(arr) {
 return arr.sort(function(a, b) {
 return a - b
 })
}
var arr = [1, 3, 2]
sortDesc(arr) // [1, 2, 3]
arr // [1, 2, 3]

函数式编程的优点