目录

🍏 JavaScript 函数式编程相关

相关问题:

  • 对 JavaScript 函数式编程的理解?
  • JavaScript 函数式编程的优缺点?

# 概念

JavaScript 函数式编程是一种「编程范式」(Programming Paradigm),一种编写程序的方法论。

主要的编程范式有三种:命令式编程、声明式编程以及函数式编程。

相比于命令式编程,函数式编程 更加强调程序的执行结果而非执行的过程,倡导利用若干的简单的执行单元让计算结果不断渐进,逐渐推导复杂的运算,而非设计一个复杂的执行过程。

🌰 例子 / 命令式编程与函数式编程比较:

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

// 函数式编程
[0, 1, 2, 3].map(num => Math.pow(num, 2))
1
2
3
4
5
6
7
8

简单来说,函数式编程就是将过程逻辑写成函数,定义好输入参数,只关心它的输出结果。即是一种描述集合与几何之间的转换关系,输入通过函数都会返回有且只有一个值。

函数实际上是一个关系(映射),这个关系是可以组合的。一旦知道函数的输出类型可以匹配一个函数的输入,就将它们可以进行组合。

# 纯函数

函数式编程旨在尽可能提高代码的无状态性和不变形。要做到这一点就要使用 无副作用的函数,也就是纯函数。

纯函数是给定输入返还相同输出的函数,并且要求所有的数据都是不可变的。即 纯函数 = 无状态 + 数据不可变。

🌰 例子:

let double = value => value * 2
1

纯函数的特点:

  • 函数内部传入指定的值,就会返回确定唯一的值;
  • 不会造成超出作用域的变化,例如修改全局变量或引用传递的参数;

纯函数的优势:

  • 使用纯函数可以产生可测试的代码;
  • 不依赖外部环境计算,不会产生副作用,提高函数的可复用性;
  • 代码可读性强,函数不管是否是一个纯函数都会有一个语义化名称,便于理解阅读;
  • 可以组装成复杂任务的可能性,符合模块化的概念以及单一职责原则。

# 高阶函数

在编程世界中,只需要处理的是 「数据」 和「关系」,而关系就是函数。

编程工作也是在找一种关系,一旦关系找到了,问题就解决了。剩下的事情就是让数据流过这种关系,转换成另一种数据。

高阶函数的作用,就是以函数作为输入或者输出的函数。通过高阶函数 抽象过程,注重结果。

🌰 例子:

const forEach = function(arr, fn) {
  for(let i = 0; i < arr.length; i++) {
    fn(arr[i])
  }
}

let arr = [1, 2, 3]
forEach(arr, (item) => {
  console.log(item)
})
1
2
3
4
5
6
7
8
9
10

forEach 是一个高阶函数,抽象了循环如何做的逻辑,然后直接关注做了什么。

高阶函数存在缓存的特性,主要利用闭包。

🌰 例子:

const once = (fn) => {
  let done = false
  return function() {
    if(!done) {
      fn.apply(this, fn)
    } else {
      console.log("该函数已执行")
    }
    done = true
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 柯里化

讲一个多参数函数转化成一个嵌套的一元函数的过程。

柯里化函数的意义:

  • 让纯函数更纯。每接受一个参数,松散结构。
  • 惰性执行。

🌰 例子:

let fn = (x, y) => x + y
1

转换为柯里化函数:

let fn = (x, y) => x + y
const curry = function(fn) {
  return function(x) {
    return function(y) {
      return fn(x, y)
    }
  }
}

let myfn = curry(fn)
console.log(myfn(1)(2))
1
2
3
4
5
6
7
8
9
10
11

🌰 例子 / 多参数的柯里化函数:

const curry = function(fn) {
  return function curriedFn(...args) {
    if (args.length < fn.length) {
      return function() {
        return curriedFn(...args.concat([...arguments]))
      }
    }
    return fn(...args)
  }
}

const fn = (x, y, z, a) => x + y + z + a
const myfn = curry(fn)
console.log(myfn(1)(2)(3)(4))
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 组合与管理

组合函数与管道函数的意义在于将多个函数组合成一个函数,完成更复杂的逻辑。

🌰 例子:

function afn(a) {
  return a * 2
}

function bfn(b) {
  return b * 3
}

const compose = (a, b) => c => a(b(c))
let myfn = compost(afn, bfn)
console.log(myfn(2))
1
2
3
4
5
6
7
8
9
10
11

compose 实现了一个简单的功能:形成了一个新的函数。而这个函数就是一条从 afn -> bfn 的流水线。

实现一个多函数组合:

const compose = (...fns) => val => fns.reverse().((acc, fn) => fn(acc), val)
1

compost 从右到左执行。

管道函数执行顺序从左到右:

const pipe = (...fns) => val => fns.reduce((acc, fn) => fn(acc), val)
1

# 优缺点

函数式编程的优点:

  • 更好的管理状态。宗旨是无状态,或者更少的状态,能最大化的减少这些未知、优化代码、减少出错的情况;
  • 更简单的复用。固定输入 和 固定输出,没有其他外部变量的影响,并且没有副作用。这样代码复用的时候,完全不需要考虑它的内部实现和外部影响;
  • 更优雅的组合。在网页中可以由各个组件组成;一个函数可以由多个小函数组成。更强的复用性,能带来更强大的组合性。
  • 隐性好处。减少代码量,提高维护性。

函数式编程缺点:

  • 性能。函数式编程相对于指令时编程,性能是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销。
  • 资源占用。在 JavaScript 中为了实现对象状态的不可变,往往会创建新的对象,因此对于垃圾回收所产生的压力远远超过其他编程方式。
  • 递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作。

# 参考

📢 上次更新: 2022/09/02, 10:18:16