title: 函数式
categories: Javascript
tag:

  • JS纯函数
    date: 2021-11-17 13:53:34

理解 JavaScript 纯函数

函数式编程中有一个非常重要的概念叫纯函数,JavaScript 符合函数式编程的范式,所以也有纯函数的概念

  • react 开发中纯函数是被多次提及的;
  • 比如react 中组件就被要求像是一个纯函数(为什么是像,因为还有 class 组件),redux 中有一个 reducer 的概念,也是要求必须是一个纯函数;
  • 所以掌握纯函数对于理解很多框架的设计是非常有帮助的;
    纯函数的维基百科定义:
  1. 在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数:
  2. 此函数在相同的输入值时,需产生相同的输出。
  3. 函数的输出和输入值以外的其他隐藏信息或状态无关,也和由 I/O 设备产生的外部输出无关。
  4. 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。

当然上面的定义会过于的晦涩,所以我简单总结一下:

  1. 确定的输入,一定会产生确定的输出;
  2. 函数在执行过程中,不能产生副作用;

关于副作用的理解

那么这里又有一个概念,叫做副作用,什么又是副作用呢?

  • 副作用(side effect)其实本身是医学的一个概念,比如我们经常说吃什么药本来是为了治病,可能会产生一些其他的副作用;
  • 在计算机科学中,也引用了副作用的概念,表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储;
    纯函数在执行的过程中就是不能产生这样的副作用:
  • 副作用往往是产生 bug 的 “温床”

纯函数的案例

我们来看一个对数组操作的两个函数:

  • slice:slice 截取数组时不会对原数组进行任何操作,而是生成一个新的数组;
  • splice:splice 截取数组, 会返回一个新的数组, 也会对原数组进行修改;

slice 就是一个纯函数,不会修改传入的参数;

  1. var names = ['abc', 'cba', 'nba', 'dna']
  2. // slice只要我们传入start,end,会给我们返回确定的值
  3. //slice不会修改原来的数组(没有副作用)
  4. var newNums = names.slice(0, 3)
  5. console.log(names)
  6. console.log(newNums)

splice 就不是纯函数

  1. var names = ['abc', 'cba', 'nba', 'dna']
  2. // splice执行时,有修改调用的数组对象本身,修改的这个操作就是产生的副作用
  3. var newNums = names.splice(2)
  4. console.log(newNums) // (2)[('nba', 'dna')]
  5. console.log(names) // (2)[('nba', 'dna')]

纯函数的优势

为什么纯函数在函数式编程中非常重要呢?

  • 因为你可以安心的编写和安心的使用;
  • 你在写的时候保证了函数的纯度,只是单纯实现自己的业务逻辑即可,不需要关心传入的内容是如何获得的或者依赖其他的外部变量是否已经发生了修改;
  • 你在用的时候,你确定你的输入内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出;

React 中就要求我们无论是函数还是 class 声明一个组件,这个组件都必须像纯函数一样保护它们的 props 不被修改:

8_函数式 - 图1

JavaScript 柯里化

柯里化也是属于函数式编程里面一个非常重要的概念。

我们先来看一下维基百科的解释:

  • 在计算机科学中,柯里化(英语:Currying),又译为卡瑞化加里化
  • 是把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术;
  • 柯里化声称 “如果你固定某些参数,你将得到接受余下参数的一个函数”;

维基百科的结束非常的抽象,我们这里做一个总结:

  • 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数;
  • 这个过程就称之为柯里化;
  1. function add(x) {
  2. return function (y) {
  3. return function (z) {
  4. return x + y + z
  5. }
  6. }
  7. }
  8. console.log(add(1)(2)(3))
  9. //简写
  10. var add2 = (x) => (y) => (z) => {
  11. return x + y + z
  12. }
  13. console.log(add2(1)(2)(3))

柯里化作用

为什么需要柯里化呢?

让函数的指责单一

  • 在函数式编程中,我们其实往往希望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个函数来处理;
  • 那么我们是否就可以将每次传入的参数在单一的函数中进行处理,处理完后在下一个函数中再使用处理后的结 果;

8_函数式 - 图2

参数的复用

另外一个使用柯里化的场景是可以帮助我们可以复用参数逻辑

  • makeAdder 函数要求我们传入一个 num(并且如果我们需要的话,可以在这里对 num 进行一些修改);
  • 在之后使用返回的函数时,我们不需要再继续传入 num 了;
  1. function makeAdder(num) {
  2. return function (count) {
  3. return num + count
  4. }
  5. }
  6. var add5 = makeAdder(5)
  7. const result = add5(10)
  8. console.log(result)

柯里化函数的实现

函数的长度等于形参的个数

  1. function add1(a, b, c, m, n) {
  2. return a + b + c + m + n
  3. }
  4. console.log(add1.length) //5.代表5个参数
  5. // 传入一个函数,返回一个新的柯里化之后的函数
  6. function DhCurrying(fn) {
  7. function curried(...args) {
  8. // 判断已经接受的参数个数,和函数本身接受的参数是否一致了
  9. if (arguments.length >= fn.length) {
  10. return fn.apply(this, args)
  11. } else {
  12. //如果参数没有达到
  13. function curried2(...args2) {
  14. return curried.apply(this, [...args, ...args2])
  15. }
  16. return curried2
  17. }
  18. }
  19. return curried
  20. }
  21. // 测试代码=========================================
  22. var curryAdd = DhCurrying(add1)
  23. var result = curryAdd(10, 20, 30, 40, 50)
  24. var result2 = curryAdd(10, 20, 30)(40, 50)
  25. var result3 = curryAdd(10)(20, 10, 40, 50)
  26. console.log(result, result2, result3)

理解组合函数

组合(Compose)函数是在 JavaScript 开发过程中一种对函数的使用技巧、模式

  • 比如我们现在需要对某一个数据进行函数的调用,执行两个函数 fn1 和 fn2,这两个函数是依次执行的;
  • 那么如果每次我们都需要进行两个函数的调用,操作上就会显得重复;
  • 那么是否可以将这两个函数组合起来,自动依次调用呢?
  • 这个过程就是对函数的组合,我们称之为 组合函数(Compose Function);
  1. function double(num) {
  2. return num * 2
  3. }
  4. function square(num) {
  5. return num ** 2
  6. }
  7. var count = 10
  8. var result = square(double(count))
  9. console.log(result) //400

那么转换为组合函数

  1. function compose(fn1, fn2) {
  2. return function (x) {
  3. return fn2(fn1(x))
  4. }
  5. }
  6. var calc = compose(double, square)
  7. console.log(calc(10))

实现组合函数

  1. function DhCompose(...fns) {
  2. var length = fns.length
  3. for (let i = 0; i < length; i++) {
  4. if (typeof fns[i] !== 'function') {
  5. throw new TypeError('Expected arguments are functions')
  6. }
  7. }
  8. function compose(...args) {
  9. //先执行第一个函数
  10. var index = 0
  11. var result = length ? fns[index].apply(this, args) : args
  12. while (++index < length) {
  13. result = fns[index].call(this, result)
  14. }
  15. return result
  16. }
  17. return compose
  18. }
  19. //测试代码=====================================================
  20. function double(m) {
  21. return m * 2
  22. }
  23. function square(n) {
  24. return n * 2
  25. }
  26. var newFn = DhCompose(double, square)
  27. console.log(newFn(10))
  28. //如果没有传入函数
  29. var newFn2 = DhCompose()
  30. console.log(newFn2(10))