纯函数 - 图1

1.概念

  1. 相同的输入永远会得到相同的输出,而且没有任何可观察的副作用。
  2. 函数式编程中的函数指的就是纯函数 他也是函数式编程的核心
  3. 纯函数就类似数学中的函数(用来描述输入和输出之间的关系),y=f(x)

image.png

  1. 函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)
  2. 当我们有很多细粒度的函数时我们可以组合成功能更强大的函数当我们在调用这些函数的时候 我们可以把一个函数的执行结果交给另一个函数去处理
  3. 对于不可控的结果 都不能算纯函数

    2.纯函数与不纯的函数对比(例子)

    ```javascript // 纯函数 slice var array = [1, 2, 3, 4, 5]

console.log(array.slice(0, 3)) console.log(array.slice(0, 3)) console.log(array.slice(0, 3)) // 每次返回都相同 // output: // [1, 2, 3] // [1, 2, 3] // [1, 2, 3]

  1. ```javascript
  2. // 不纯的函数 splice
  3. var array = [1, 2, 3, 4, 5]
  4. console.log(array.splice(0, 3))
  5. console.log(array.splice(0, 3))
  6. console.log(array.splice(0, 3))
  7. // 每次返回都不相同
  8. // 不符合相同的输入永远会得到相同的输出
  9. // output:
  10. // [1, 2, 3]
  11. // [4, 5]
  12. // []

3.Lodash:纯函数的代表

  1. 安装:
    1. 初始化 package.json:npm init -y
    2. 安装lodash:npm i lodash
    3. 引用loadash:const _ = require(‘lodash’) 官网一般定义为下划线
  2. 示例 ```javascript // loadash 演示 // first(获取头元素) / last(获取尾元素) / toUpper(转大写) / reverse(反转数组) / each(循环) / // js数组中需要ES6 includes(查找是否包含) / find(找出第一个符合条件的数组成员) / findIndex(返回第一个符合条件的数组成员的位置) const _ = require(‘lodash’);

const arr = [‘aaa’,’bbb’,’ccc’,’bbb’,’ddd’]

console.log(.first(arr)) console.log(.first(arr)) console.log(.last(arr)) console.log(.last(arr))

console.log(.toUpper(.first(arr))) console.log(.toUpper(.first(arr)))

// reverse 不是纯函数 console.log(.reverse(arr)) console.log(.reverse(arr))

const r = _.each(arr,(item,index)=>{ console.log(item,index) }) console.log(r);

console.log(.includes(arr,’aaaa’)); console.log(.find(arr,function(value){return value === “bbb”})); console.log(_.findIndex(arr,function(value){return value === “bbb”}));

  1. <a name="c8GKq"></a>
  2. ## 4.好处
  3. <a name="6YCQ6"></a>
  4. #### 1. 可以缓存,可以提高程序性能
  5. 因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来
  6. ```javascript
  7. // 模拟一个 memoize 函数
  8. const _ = require('lodash');
  9. function getArea(r) {
  10. console.log(r);
  11. return Math.PI * r * r;
  12. }
  13. // let getAreaWithMemory = _.memoize(getArea);
  14. // console.log(getAreaWithMemory(5));
  15. // console.log(getAreaWithMemory(5));
  16. // console.log(getAreaWithMemory(5));
  17. // 模拟memoize 缓存
  18. function memoize(fn) {
  19. // 定义一个对象把fn执行结果存储起来
  20. let cache = {}
  21. return function () {
  22. let arg_str = JSON.stringify(arguments);
  23. // 如果直接取到结果就返回没取到就调用一下fn apply(在 func 函数运行时使用的 this 值,数据)
  24. cache[arg_str] = cache[arg_str] || fn.apply(fn, arguments);
  25. return cache[arg_str]
  26. }
  27. }
  28. let getAreaWithMemory = memoize(getArea);
  29. console.log(getAreaWithMemory);
  30. console.log(getAreaWithMemory(5));
  31. console.log(getAreaWithMemory(5));
  32. console.log(getAreaWithMemory(5));
  33. //output:
  34. //5
  35. //78.53981633974483
  36. //78.53981633974483
  37. //78.53981633974483

运行代码可以看到console.log(r);// 5 只执行了一次

2. 可移植性/自文档化(Portable / Self-Documenting)

  1. 纯函数是完全自给自足的,它需要的所有东西都能轻易获得。
  2. 纯函数的依赖很明确,因此更易于观察和理解——没有偷偷摸摸的小动作。

    3. 可测试:纯函数让测试更方便

    因为纯函数始终有输入和输出
    而单元测试是在断言这个函数的结果
    所以所有的纯函数都是可测试的函数

    4. 合理性(Reasonable)

    纯函数最大的好处是引用透明性(referential transparency)如果一段代码可以替换成它执行所得的结果,而且是在不改变整个程序行为的前提下替换的,那么我们就说这段代码是引用透明的。
    由于纯函数相同的输入永远会得到相同的输出,这也就保证了引用透明性

    5. 并行处理

    在多线程环境下并行操作共享的内存数据很可能会出现意外情况
    纯函数不需要访问共享的内存数据(他是一个封闭的空间因为纯函数只依赖于参数),所以在并行环境下可以任意运行纯函数 ( Web Worker ES6 以后)

    5.副作用

  3. 副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。

  4. 副作用让一个函数变的不纯(如示例),纯函数的根据相同的输入返回相同的输出,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用。
  5. 来源
    1. 如配置文件、数据库、获取用户输入等
    2. 所有外部交互都有可能带来副作用
    3. 副作用也使得方法通用性下降不适合扩展和可重用性,同时副作用会给程序中带来安全隐患给程序带来不确定性,但是副作用不可能完全禁止,尽可能控制它们在可控范围内发生。 ```javascript // 不纯的 let mini = 18 function checkAge (age) { return age >= mini }

// 纯的(有硬编码,后续可以通过柯里化解决) function checkAge (age) { let mini = 18 return age >= mini } ``` 所有的外部交互都有可能带来副作用,副作用也使得方法通用性下降不适合扩展和可重用性,同时副作用会给程序中带来安全隐患给程序带来不确定性,但是副作用不可能完全禁止,尽可能控制它们在可控范围内发生。