lgwebdream/FE-Interview

Day247:说一下对纯函数的理解

Opened this issue · 1 comments

每日一题会在下午四点在交流群集中讨论,五点小程序中更新答案
欢迎大家在下方发表自己的优质见解
二维码加载失败可点击 小程序二维码

扫描下方二维码,收藏关注,及时获取答案以及详细解析,同时可解锁800+道前端面试题。


一、概念

1.1 什么是纯函数

纯函数是函数式编程的基础。

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。

他的重点在于相同的输入,永远会得到相同的输出

1.2 什么是副作用

“作用”我们可以理解为一切除结果计算之外发生的事情。

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

副作用可能包含,但不限于:

  • 更改文件系统
  • 往数据库插入记录
  • 发送一个 http 请求
  • 可变数据
  • 打印/log
  • 获取用户输入
  • DOM 查询
  • 访问系统状态
  • ...

概括来讲,只要是跟函数外部环境发生的交互就都是副作用。函数式编程的哲学就是假定副作用是造成不正当行为的主要原因。

二、纯函数两个特点

2.1 相同的输入得到相同的输出

先看个不纯的反面典型

let a = 1;
function add(b) {
  return a + b;
}
console.log(add(2)); //3

上面代码中,add(2),是不是永远返回 3,显然是不是的。假如我们修改了 a 的值,就会影响 add 函数的输出。即函数 add 其实是依赖外部状态的。

如果想改成纯的函数,可以这样改:

function add(b) {
  let a = 1;
  return a + b;
}
console.log(add(2)); //3

这样对于任何输入参数,都有与之对应的唯一输出参数了,该函数就符合纯函数的特点了。

2.2 没有副作用

先来看个反面典型:

const user = {
  name: "yideng",
};
let isValid = false;
function check(user) {
  if (user.name.length > 4) {
    isValid = true;
  }
}

可以看到,执行函数的时候会修改到 isValid 的值,这样就存在副作用了,这个函数的运行,修改了外部的状态。

那可以怎么处理移除这个副作用呢?其实不需要修改外部的 isValid 变量,我们只需要在函数中将验证的结果 return 出来:

const user = {
  name: "yideng",
};
function check(user) {
  return user.name.length > 4;
}
const isValid = check(user);

这样 check 函数就不会修改任何外部的状态了~

三、为什么要使用纯函数

3.1 可缓存性

纯函数可以根据输入来做缓存。实现缓存的是一种叫作 memorize 的技术。

看下 vue 源码中的一段代码:

/**
 * Create a cached version of a pure function.
 * 只适用于缓存 接收一个字符串为参数的 fn
 */
export function cached(fn) {
  const cache = Object.create(null);
  return function cachedFn(str) {
    const hit = cache[str];
    return hit || (cache[str] = fn(str));
  };
}

/**
 * Capitalize a string.
 */
export const capitalize = cached((str) => {
  return str.charAt(0).toUpperCase() + str.slice(1);
});

capitalize 即为缓存后的函数,如果多次调用就会返回缓存后的值,从而节省计算资源,而这一切的前提都建立在传入 cached 中的那个函数为纯函数的基础上。

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

由于纯函数是自给自足的,它需要的东西都在输入参数中已经声明,所以它可以任意移植到任何地方。

3.3 可测试性

函数让测试更加容易。我们不需要伪造一个“真实的”支付网关,或者每一次测试之前都要配置、之后都要断言状态(assert the state)。只需简单地给函数一个输入,然后断言输出就好了。

看个反面的例子,使用上边不纯的 add 方法来做单元测试:

// jest 语法
describe("add", function () {
  it("add", function () {
    expect(add("2")).toEqual(3);
  });
});

如果我们修改了 a 为 3,上面的测试就会失败了。

它应始终返回相同的值。不管调用该函数多少次,无论今天、明天还是将来某个时候调用它。
自包含(不使用全局变量)。
它不应修改程序的状态或引起副作用(修改全局变量