函数式编程简介

2025-05-24

函数式编程简介

函数式编程已经存在一段时间了,但它真正开始受到人们的关注。它是一种不同于面向对象编程的编程方法,它改变了你思考问题和数据的方式。你不再关注如何做事,而是关注事物本身。如果你像我们大多数人一样,一直在面向对象的世界中工作,那么适应函数式编程可能需要一段时间。

然而,一旦你开始调整,它就会彻底改变你对优秀程序的认知。你不再需要通过疯狂的异步调用(数据类型可以随时更改)来追踪错误,而是需要方法在输入相同数据时始终返回相同的值。你的代码几乎无 bug,这有点疯狂。我们将介绍函数式编程的背景知识,并分析一些 JavaScript 示例,最后总结一些你应该使用函数式编程的理由。

函数式编程的背景

函数式编程的主要目标是能够像数学方程式那样始终如一地重现值。你希望确保输入的数据始终能够返回正确的值,而函数式编程正是实现了这一点。它采用声明式编程方法。通常,我们会描述处理数据的步骤,而不是直接描述数据本身。以下是函数式方法与面向对象方法的对比示例。

问题:获取用户购物车的总额

面向对象

将总变量设置为零,
将每件商品的价格放入数组中,
对数组中的价格求和,
加上税费和运费,
得到总计

功能

用户购物车的总额是所有商品价格加上税费和运费的总和

这是函数式编程和面向对象编程的核心区别。函数式编程有三个主要原则让我们能够以这种方式编写代码:不变性、数据和函数分离以及一等函数。

不变性

不变性可以处理诸如变量管理之类的复杂问题。在面向对象编程中,你通常会为变量赋值,而这些值随时可能发生变化。随着应用程序规模扩大到使用数千个变量,保持变量值与当前状态同步会变得非常困难。面对如此多的变量,追踪 bug 变得越来越困难。

函数式编程通过将每个变量视为一个值来解决这个问题。它不是被赋值,而是本身就是一个值。例如,假设你的系统中有一个用户,你想赋予他们新的权限。通常你会这样做。

let user = new User('contributor', ['view', 'write']);

user.addPermission('edit');
Enter fullscreen mode Exit fullscreen mode

使用函数式编程,您可以做类似这样的事情。

const user = {
    role: 'contributor',
    permissions: ['view', 'write']
};

const updatedUser = {
    role: user.role,
    permissions: [user.permissions].push('edit')
};
Enter fullscreen mode Exit fullscreen mode

你会注意到,大多数变量都声明为 const,这是基于不变性原则的。这使得你可以从一组不可变的初始数据集开始并保存,这意味着你的状态拥有一个明确的单一真实来源。当你需要更改数据时,你可以创建一个新的变量来保存新的值。这意味着每次使用完全相同的数据集运行这段代码时,你都会得到完全相同的结果。

数据和功能分离

对于有面向对象编程背景的人来说,这是最棘手的部分。在函数式编程中,你必须将数据与代码分离。这里不允许双向绑定。你不需要处理 getter、setter 以及引用其他类的类,而是传入你想要函数处理的数据。这些数据不包含在类的属性中,而你需要管理属性的状态。

你正在使用一个常量链,由于其不可变性,它不会改变传递给它的任何数据的值。因此,如果你正在处理类似数组的东西,并且需要更改某个值,你可以复制该数组并对其进行更新。这是一个在简单的预算跟踪应用中分别以面向对象和函数式的方式实现数据和函数分离的示例。

面向对象

class PurchaseList {
    constructor(purchases) {
        this._purchases = purchases;
    }

    addPurchase(purchase) { /* do stuff */ };
}

class Purchase {
    constructor(item, price, date) {
        this._item = item;
        this._price = price;
        this._date = date;
    }

    getItem() {return this._item };
}
Enter fullscreen mode Exit fullscreen mode

功能

const purchase1 = {
    item: 'toilet paper',
    price: 12.47,
    date: 2019-10-09
};

const purchase2 = {
    item: 'plant food',
    price: 10.87,
    date: 2018-10-09
};

const purchaseList = [
    purchase1,
    purchase2
];
Enter fullscreen mode Exit fullscreen mode

从代码角度来看,数据与函数的分离就是这样的。函数式编程主要处理 JavaScript 中的数组和对象,因此请确保你非常熟悉数组和对象的方法。

一等函数

这是函数式编程最有趣的部分之一。你可以像对待其他数据类型一样对待函数。这意味着你可以将函数作为参数传递,也可以从其他函数调用中返回函数。这就引出了纯函数的概念。纯函数是指不依赖于函数外部任何状态的函数。

纯函数唯一需要担心的数据是传递给它的数据。对于纯函数来说,唯一会导致结果不同的方式是传入不同的值。返回的结果不会受到函数外部任何数据的影响。函数式编程的目标之一是尽可能保持函数的纯粹性,以避免状态管理问题。

当你的函数大多数都是纯函数时,你可以将这些纯函数用作其他函数的“参数”,因为你知道纯函数完全独立于其他函数。我们将创建一个纯函数的示例,并看看它作为参数传递时是如何使用的。

转换数组示例

为了展示函数式编程如何在你可能用到的程序中发挥作用,我们将通过一个示例来演示如何编写一个将数组转换为其他类型的函数。假设你有一个用于电商应用的数组,其中包含未排序、未计数的商品。你想返回一个包含每件商品名称和数量并显示给用户的对象。以下是如何用函数式编程来实现这一点的。

const inventory = ['popsicle', 'underwear', 'sauce', 'pens', 'potatoes', 'sauce', 'onion', 'onion', 'pens', 'potatoes', 'ukulele', 'tomahawk', 'underwear', 'popsicle', 'sauce', 'ukulele', 'onion', 'underwear', 'popsicle', 'potatoes', 'onion', 'pens', 'ukulele'];

const countItems = inventory => {
    return inventory.reduce((acc, name) => ({
        acc,
        [name]: acc[name] ? acc[name] + 1 : 1
    }), {});
};
Enter fullscreen mode Exit fullscreen mode

我们在这里所做的是创建一个名为 countItems 的函数,它接收一个名为 inventory 的数组作为参数。然后我们使用 reduce 数组方法将此数组转换为一个对象。由于 reduce 方法需要一个起始点,因此我们传入一个空对象作为该方法的第二个参数。在数组内部,我们对 acc 变量使用展开运算符,将目前拥有的名称和数量放入要返回的对象中。

然后,我们获取数组中当前所在的名称。我们检查 acc 变量,如果其中还没有当前名称,就将其计数初始化为 1。之后,它会遍历整个数组,并不断进行检查和计数。有几个因素使这个函数成为纯函数。

首先,你会注意到它不依赖任何外部变量。其次,你会注意到我们使用了扩展运算符,而不是实际的 acc 变量。这保持了不变性原则,因为我们没有改变原始变量。最后,数据与函数完全分离。即使多次传入初始数组,你也始终会得到相同的结果,并且传入的任何其他数组也一样。

使用函数式编程的原因

函数式编程是一种不同于面向对象编程的编程方法,它能够解决面向对象编程 (OOP) 的诸多问题。首先,它能够帮助你避免几乎所有的 bug,并使你的代码更具可读性。由于函数的结果始终相同,它使你的应用程序整体上更易于维护、更可靠、更可扩展。此外,你无需过多担心状态管理,因为你的函数或变量不像面向对象编程 (OOP) 那样严重依赖状态。

如果你有面向对象编程 (OOP) 背景,那么在函数式编程范式中思考需要一些时间来适应。不过,一旦你习惯了,就很难再回到面向对象编程了,因为你会注意到函数式编程解决的所有问题。你的代码更简洁,而且数据不会出现意外的变化,这让人耳目一新。

你觉得怎么样?我非常喜欢函数式编程,尤其是在生产环境中。你有过函数式编程的经验吗?无论好坏。


嘿!你应该在 Twitter 上关注我,理由如下:https://twitter.com/FlippedCoding

文章来源:https://dev.to/flippedcoding/intro-to-function-programming-2n1g
PREV
我希望在开始 Web 开发之前了解的事情
NEXT
在 Node.JS 中实现无密码身份验证