7.JavaScript中的纯函数和副作用是什么?

411 阅读4分钟

JavaScript中的纯函数和副作用是什么?

纯函数和副作用是函数式编程中众所周知的概念。这些概念在JavaScript中也被广泛使用。

JavaScript函数简介

函数允许我们逻辑地放置代码来执行任务。函数​是JavaScript编程语言中的一等公民。您可以创建、修改函数,将其用作另一个函数的参数,或从函数返回。您还可以将函数作为值分配给变量。简而言之,如果不使用函数,您几乎不会使用或编写任何有用的JavaScript代码。

在本文中,我们将了解纯函数​及其优势。我们还将研究副作用​及其影响。

一个函数可以接受零个或多个输入并产生一个输出。您可以显式返回函数的输出,或者它只返回一个​undefined​。

显式返回值的函数

function testMe(input) { 
	return `testing ${input}`;
} 

testMe(123); 

不显式返回值的函数

function testMe() {
   
} 
testMe(); 

因此,当我们了解了基本用法后,让我们看看今天的纯函数​主题。我们还将了解概念、副作用​及其对纯函数的影响。

纯函数和副作用与示例

作为软件程序员/开发人员,您编写源代码以根据输入生成输出。通常,您编写函数​来根据输入执行任务并生成输出。我们需要确保这些函数是

  • 可预测​:它为相同的输入产生可预测的输出。
  • 可读性:任何将函数作为独立单元阅读的人都可以完全理解其用途。
  • 可重用:可以在源代码的多个位置重用函数,而不会改变其和调用者的行为。
  • 可测试:我们可以将其作为一个独立的单元进行测试。

纯函数​具有上述所有特征。它是一个为相同输入产生相同输出的函数。这意味着当您传递相同的参数时,它会返回相同的结果。纯函数​不应该有任何副作用​来更改预期的输出。

下面的函数​sayGreeting()​是一个纯函数。你能猜出为什么吗?

function sayGreeting(name) { 
	return `Hello ${name}`;
}

它是一个纯函数,因为您总是得到一个​Hello <name>​作为​<name>​传递的输出作为输入。现在,让我们看看相同的函数,但有一点变化。

let greeting = "Hello";  
function sayGreeting(name) { 
	return `${greeting} ${name}`;
}

它是一个纯粹的函数吗?嗯,不是。该函数的输出现在依赖于一个名为​greeting​的外部状态。如果有人将问候变量的值更改为Hola​怎么办?即使您传递相同的输入,它也会更改​sayGreeting()​函数的输出。

sayGreeting('Alex'); 

sayGreeting('Alex'); 

因此,在这里,我们已经看到了依赖于外部状态值的副作用,该值可能会在函数没有意识到的情况下发生变化。

一些更典型的副作用案例是

  • 变异(改变)输入本身。
  • 查询/更新DOM
  • 日志记录(即使在控制台中)
  • 进行XHR/fetch调用

任何与函数的最终输出没有直接关系的操作都称为副作用​。现在让我们看看一个不纯函数​,我们改变输入并做一些我们不应该在纯函数​中做的事情。

function findUser(users, item) { 
	const reversedUsers = users.reverse(); 
	const found = reversedUsers.find((user) => { 
		return user === item; 
	}); 
	document.getElementById('user-found').innerText = found;
}

上面的函数有两个参数,一个users(一个数组)和一个要在数组中查找的item。它通过反转从数组末尾找到项目。一旦在数组中找到item,它就使用DOM方法将该值设置为文本到超文本标记语言元素。

这里我们打破了纯函数​的两个基本原则

  1. 我们正在改变输入
  2. 我们正在查询和操作DOM

那么,我们可以预见什么样的问题呢?让我们看看。调用者将通过以下方式调用​findUser()​函数,

let users = ['Tapas', 'Alex', 'John', 'Maria'];
findUser(users, 'Maria');

在这个阶段,除非调用者读取​findUser()​函数代码,否则调用者可能不知道函数正在进行DOM操作。因此,易读性​受到影响。函数的输出正在执行与最终输出无关的操作。

此外,我们还对输入数组进行了变异。理想情况下,我们应该克隆输入,然后对查找操作的副本进行reverse(反转)。现在让我们将其设为纯函数。

function findUser(users, item) { 
	const reversedUsers = [ ...users].reverse(); 
	const found = reversedUsers.find((user) => { return user === item; }); 
	return found;
}

然后

let users = ['Tapas', 'Alex', 'John', 'Maria'];
let found = findUser(users, 'Maria');

现在​findUser()​函数是一个纯函数。我们已经消除了改变输入的副作用,它返回预期的输出。因此,该函数作为一个单元是可读的、可测试的、可重用的和可预测的。

纯函数及相关行话

纯函数和副作用是函数式编程​的概念。您可能会遇到一些需要友好澄清的行话。

  • 引用透明度​:这意味着我们应该能够在不改变程序行为的情况下用其产出值替换函数调用(或调用)。如你所见,只有当函数是纯函数​时才有可能。让我们举一个简单的纯函数,

    function multipication(x, y) { 
    	return x * y;
    }
    

    所以,现在在这个表达式中,我们可以用它的产出值替换函数调用,保证没有副作用​,

    10 + (multiplication(6, 3) ^ 2);
    

    相当于

    10 + (18 ^ 2);
    
  • 并行代码:纯函数有助于并行代码执行。但是,在JavaScript中,代码默认按顺序运行。

那么,我可以让所有函数都成为纯函数​吗?

是的,从技术上讲,你可以。但是只有纯功能的应用程序可能做不了什么。

你的应用程序会有一些副作用,比如HTTP调用、登录到控制台、IO操作等等。请在尽可能多的地方使用纯函数。尽可能隔离不纯的函数(副作用)。这将大大提高你的程序的易读性、可调试性和可测试性。

像纯函数一样接受函数式编程概念,减少副作用将使您的代码更好地管理和维护。这意味着更少的错误、快速识别问题、隔离问题、增加可重用性和可测试性。