JavaScript 函数式编程指南 读书笔记

发布于 2022-09-27 14:02:02 字数 4070 浏览 197 评论 0

纯函数

纯函数:相同输入永远相同输出且无任何可观察的副作用。如 slice 和 splice,可以把 slice 看作是纯函数,而 splice 不是,因为 splice 修改原数组,违背了了相同输入不同输出的原则。

纯函数借鉴于数学中函数的概念,即一对一,而不是一对多(即相同输入永远相同输出)

纯函数的输出结果不应该依赖外部的环境,如:

// 不纯
const minimum = 21;
const checkAge = function(age){
    return age >= minimum
}

// 纯的

const checkAge = function(age) {
     const minimum = 21;
     return age >= minimum;
}

很明显,对于不纯的函数的实现, minimum 变量在外部环境中,而这个变量随时都可能被更改

在 JavaScript 中,我们可以使用 Object.freeze 方法将一个变量变为不可变对象,这样状态不会变化,也就保持了其 纯粹性

const immutableState = Object.freeze({
    minimum: 21
});

immutableState.minimumu;
// 21

immutableState.minimumu = 32;
// 严格模式下将会报错

immutableState.minimumu;
// 21

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

  • 更改文件系统
  • 更改数据库记录
  • 发送请求
  • 改变外部数据
  • DOM 查询
  • 访问系统状态

只要是 跟外部环境发生的交互 都是副作用,面对副作用,不是要完全禁止,而是应该让这些副作用变得 可控

纯函数的好处

可缓存

对输入进行缓存(得益于一对一原则)

const squareNumber = memoize(function(x){
    return x * x;
})

squareNumber(4);
// 16        首次计算


squareNumber(4);
// 16        从缓存中直接读取结果

一个简单的记忆函数实现:

const memoize = function(f) {
    const cache = {};
    return function(){
        const arg_str = JSON.stringify(arguments);
        cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
        return cache[arg_str];
    }
}

可移植性

可移植性可以意味着把函数序列化(serializing)并通过 socket 发送。也可以意味着代码能够在 web workers 中运行。总之,可移植性是一个非常强大的特性

可测试性

只需要简单的输入,然后对输出断言即可。

合理性

相同输入永远相同输出。

引用透明性 :如果一段代码的 执行结果 可以 替换 成这段 代码,并且不改变整个程序的行为,那么这段代码是引用透明的。

纯函数允许我们并行执行任意的纯函数,因为不需要访问共享的内存,也就不会进入竞争态。对于JavaScript来说服务端的Nodejs环境与 web worker 的浏览器都可以实现。

柯里化(curry)

概念:把接受多个参数的函数转化为接受一个单一参数的(最初函数的第一个参数)的函数,并且返回接受余下参数并且返回结果的新函数的技术。

const add = function(x) {
    return function(y) {
        return x + y;
    }
}


const addTen = add(10);

addTen(2);
//  12

很明显,上文柯里化的秘密在于闭包

使用 lodash curry 方法的例子:

const curry = require("lodash").curry;

const match = curry(function(what, str){
    return str.match(what);
});

const replace = curry(function(what, replacement, str){
    return str.replace(what, replacement);
})

使用:

match(/s+/g, "hello world");
// [" "]

match(/s+/g)("hello world");
// [" "]

const  hasSpaces = match(/s+/g);

hasSpaces("hello world");
// [" "]

可想而知,这种看似“预加载”的能力,在大型项目中使用可以大大简化代码。

柯里化非常符合纯函数的定义,一个输入准确对应于一个输出,当然为了减少()的调用,curry函数同样可以一次传递多个参数

代码组合(compose)

一个简单的例子:

const compose = function(f, g) {
    return function(x){
        return f(g(x));
    }
}

使用 compose 将字母转大写与末尾添加 ! 的两个函数组合,如此一来两个函数产生的奇妙的变化,两个函数的 魔法 的作用将组成起一个崭新的函数。

const toUpperCase = function(x) {
    return x.toUpperCase();
};

const exclaim = function(x) {
    return x + '!';
};

cosnt shout = compose(exclaim, toUpperCase);

shout("good student");
// "GOOD STUDENT!"

代码对于 compose 函数来说是 右参数 先与 左参数 执行,这样就是一个 从右到左 的数据流。

一个顺序很重要的例子:

const head = function(x){
    return x[0];
}

const reverse = reduce(function(acc, x) {
    return [x].concat(acc);
}, []);

const last = compose(head, reverse);

从右向左执行更加反映数学上的含义。而且实际上,所有的组合都有一个 共性 就是 符合结合律

const associative = compose(f, compose(g, h)) == compose(compose(f,g), h);

既然符合结合律,也就意味着调用分组并不重要,也就是说可以无关参数顺序了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

小嗷兮

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

烙印

文章 0 评论 0

singlesman

文章 0 评论 0

独孤求败

文章 0 评论 0

晨钟暮鼓

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文