这是用户在 2024-9-25 20:16 为 https://dmitripavlutin.com/javascript-pure-function/ 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
Home
Dmitri Pavlutin
I help developers understand Frontend technologies
我帮助开发者理解前端技术
Post cover

Pure Functions in JavaScript: A Beginner's Guide
JavaScript 中的纯函数:初学者指南

Posted May 15, 2023 发布于 2023 年 5 月 15 日

A function is a reusable block of code that accepts arguments and returns a computed value.
函数是一个可重用的代码块,它接受参数并返回计算值。

A pure function always returns the same value given the same arguments and produces no side effects.
一个纯函数在给定相同参数时总是返回相同的值,并且不产生副作用。

Let's see in more detail what are pure functions and why they are useful.
让我们更详细地看看什么是纯函数以及它们为什么有用。

1. Pure functions 纯函数

A function that returns the sum of 2 numbers is pure:
一个返回两个数字之和的函数是纯粹的:

function sum(a, b) {
return a + b
}

console.log(sum(1, 2)) // logs 3
console.log(sum(1, 2)) // logs 3

console.log(sum(5, 2)) // logs 7
console.log(sum(5, 2)) // logs 7

sum() is a pure function because, given the same numbers, it always returns the same sum.
sum() 是一个纯函数,因为在给定相同数字的情况下,它总是返回相同的和。

For example, sum(1, 2) always returns 3, no matter how many times or where the function is called.
例如,sum(1, 2) 无论函数被调用多少次或在哪里调用,总是返回 3

Pure function JavaScript

Let's see some more examples of pure functions:
让我们看看一些纯函数的更多例子:

// max of arguments
Math.max(1, 2, 3)

// nearest lowest integer
Math.floor(1.23)

// The multiplication of two numbers
function multiply(a, b) {
return a * b
}

// Summarizing the array items
function sumOfArray(array) {
return array.reduce((sum, item) => sum + item)
}

// Returning a constant value
function answer() {
return 42
}

// Function that returns nothing (noop)
function noop() {
// nothing
}

Now let's look at the second requirement of a pure function: do not produce a side effect.
现在让我们来看一下纯函数的第二个要求:不产生副作用。

A side effect is change to external state or environment outside of the function scope. Examples of side effects are:
副作用是对外部状态或环境的改变,超出了函数范围。副作用的例子包括:

  • changing variables and objects defined outside the function scope
    在函数作用域外定义的变量和对象的变化
  • logging to console 记录到控制台
  • changing document title 更改文档标题
  • DOM manipulations DOM 操作
  • making HTTP requests 发起 HTTP 请求

If the sum() function logs to the console, then the function is not pure because it produces a side effect:
如果 sum() 函数记录到控制台,那么该函数就不是纯函数,因为它产生了副作用:

function sumSideEffect(a, b) {
const s = a + b
console.log(s) // Side effect!
return s
}

console.log(sumSideEffect(1, 2))

sumSideEffect() produces a side effect. It is not a pure function.
sumSideEffect() 产生了副作用。它不是一个纯函数。

Functions that are not pure are called impure. Before looking at the impure functions, let's see what are the benefits of pure functions.
不纯的函数称为不纯。在查看不纯函数之前,让我们先看看纯函数的好处。

2. Pure function benefits
2. 纯函数的好处

The main benefit of a pure function is predictability: given the same arguments it always returns the same value.
纯函数的主要好处是可预测性:给定相同的参数,它总是返回相同的值。

The pure function is also easy to test. The test just has to supply the right arguments and verify the output:
纯函数也是易于测试的。测试只需提供正确的参数并验证输出:

describe('sum()', () => {
it('should return the sum of two numbers', () => {
expect(sum(1, 2)).toBe(3)
})
})

Because the pure function doesn't create side effects, the test doesn't have to arrange and clean up the side effect.
因为纯函数不产生副作用,测试不必安排和清理副作用。

The pure function that makes computationally expensive calculations can be memoized. Because the single source of truth of a pure function is its arguments they can be used as cache keys during memoization.
可以对进行计算开销大的计算的纯函数进行记忆化。因为纯函数的唯一真相来源是它的参数,所以它们可以在记忆化过程中用作缓存键。

factorial() function is a pure function. Because factorial computation is expensive, you can improve the performance of the function by wrapping factorial into a memoize() wrapper (see the npm package):
factorial() 函数是一个纯函数。由于阶乘计算开销较大,您可以通过将阶乘包装到 memoize() 包装器中来提高函数的性能(请参见 npm 包):

import memoize from 'lodash.memoize'

function factorial(n) {
if (n === 0) {
return 1
}
return n * factorial(n - 1)
}
const memoizedFactorial = memoize(factorial)

console.log(memoizedFactorial(5)) // logs 120
console.log(memoizedFactorial(5)) // logs 120

Open the demo. 打开演示。

When calling memoizedFactorial(10) the memoized factorial with argument 5, the factorial function itself is going to be invoked and the result is memoized.
当调用 memoizedFactorial(10) 时,带有参数 5 的备忘录阶乘将会被调用,阶乘函数本身将被执行,并且结果会被备忘录。

Calling again the memoized factorial with the same 5 arguments returns the memoized value right away.
再次调用带有相同 5 参数的备忘录阶乘会立即返回备忘录值。

Pure Function are Memoized

Pure functions are easy to compose. Simple pure functions can be composed to create more complex functions.
纯函数易于组合。简单的纯函数可以组合以创建更复杂的函数。

For example, you can use reuse the pure sum() function to calculate the sum of an array:
例如,您可以重用纯 sum() 函数来计算数组的总和:

function sum(a, b) {
return a + b
}

function sumOfArray(array) {
return array.reduce(sum)
}

console.log(sumOfArray([2, 3])) // logs 5

Pure functions are the base of functional programming. I encourage you to explore the popular functional programming library Ramda, which uses extensively the composition of pure functions.
纯函数是函数式编程的基础。我鼓励你探索流行的函数式编程库Ramda,它广泛使用纯函数的组合。

3. Impure functions 3. 不纯函数

A function that can return different values given the same arguments or makes side effects is named impure function.
一个可以在给定相同参数时返回不同值或产生副作用的函数被称为不纯函数

In practice, a function becomes impure when it reads or modifies an external state.
在实践中,当一个函数读取或修改外部状态时,它变得不纯。

A good example of an impure function is the built-in JavaScript random generator Math.random():
一个不纯函数的一个好例子是内置的 JavaScript 随机生成器 Math.random()

console.log(Math.random()) // logs 0.8891108266488603
console.log(Math.random()) // logs 0.9590062769956789

Math.random(), given the same arguments (in this case no arguments at all), returns different numbers smaller than 1. This makes the function impure.
Math.random(),在给定相同参数的情况下(在这种情况下没有参数),返回小于 1 的不同数字。这使得该函数不纯。

Impure function in JavaScript

Here's another example of an impure function:
这是另一个不纯函数的例子:

let value = 0

function add(increase) {
value += increase // Side-effect
return value
}

console.log(add(2)) // logs 2
console.log(add(2)) // logs 4

add() function is impure because it produces a side effect: modifies value variable accessed from the outer scope. The function also returns different values for the same arguments.
add() 函数是不纯的,因为它产生了副作用:修改了从外部作用域访问的 value 变量。该函数对于相同的参数也返回不同的值。

Impure Function with Side Effect

Other examples of impure functions:
其他不纯函数的例子:

function addProperty(object) {
// Mutates the parameter object (side effect)
Object.assign(object, { b: 1 })
}

function deleteById(id) {
// Modifies DOM (side effect)
document.getElementById(id).remove()
}

async function fetchEmployees() {
// Accesses the networks (external state)
const response = await fetch('https://example.com/employees/')
return response.json()
}

function screenSmallerThan(pixels) {
// Accesses the browser page (external state)
const { matches } = window.matchMedia(`(max-width: ${pixels})px`)
return matches
}

These functions are impure because they make side effects like mutating the parameter or DOM and accessing external states like the network and the screen information.
这些函数是不纯的,因为它们会产生副作用,例如改变参数或 DOM,以及访问外部状态,如网络和屏幕信息。

4. Dealing with impure functions
处理不纯函数

Impure functions have a higher complexity compared to pure functions. Complexity is added by accessing external states or by side effects.
不纯函数相比于纯函数具有更高的 复杂性。复杂性是由于访问外部状态或副作用而增加的。

Because of their higher comlexity impure functions are harder to test. You have to mock the external state or the side effect to understand if the function works correctly.
由于它们的复杂性,纯函数更难测试。您必须模拟外部状态或副作用,以了解函数是否正常工作。

Either way, there's nothing wrong with the impure functions. They are a necessary evil for the application to communicate with the external world.
无论如何,非纯函数并没有错。它们是应用程序与外部世界沟通的必要之恶。

If you are lucky, some impure functions can be transformed into pure by refactoring mutable operations to immutable.
如果你幸运的话,一些不纯的函数可以通过将可变操作重构为不可变来转化为纯函数。

The following function adds default properties to an object. The function is impure because the parameter original is mutated:
以下函数向对象添加默认属性。该函数是不纯的,因为参数 original 被改变:

function addDefaultsImpure(original, defaults) {
return Object.assign(original, defaults)
}

const original = { a: 1 }
const result = addDefaultsImpure(original, { b: 2 })

console.log(original) // logs { a: 1, b: 2 }
console.log(result) // logs { a: 1, b: 2 }

Object.assign(object, defaults) mutates original parameter by merging the properties of defaults object into it.
Object.assign(object, defaults) 通过将 defaults 对象的属性合并到 original 参数中进行变异。

The problem with addDefaultsImpure() is the cognitive load: you have to remember that the argument object is mutated.
问题在于 addDefaultsImpure() 的认知负担:你必须 记住 参数对象是被改变的。

Let's make the function pure by using an immutable merge operation:
让我们通过使用不可变的合并操作来使函数变得纯粹:

function addDefaultsPure(original, defaults) {
return Object.assign({}, original, defaults)
}

const original = { a: 1 }
const result = addDefaultsPure(original, { b: 2 })

console.log(original) // logs { a: 1 }
console.log(result) // logs { a: 1, b: 2 }

Object.assign({}, object, defaults) doesn't alter neither original nor defaults objects. It just creates a new object.
Object.assign({}, object, defaults) 不会改变 originaldefaults 对象。它只是创建一个新对象。

addDefaultsPure() is now pure and has no side effects.
addDefaultsPure() 现在是纯的,没有副作用。

Another approach I have found efficient is the extraction of big chunks of pure code from an impure function. Then make the impure function call the extracted pure function.
另一种我发现有效的方法是从一个不纯的函数中提取大量纯代码。然后让不纯的函数调用提取的纯函数。

This gives the benefit of isolating the logic that is understandable and predictable into a pure function. The complexity of the impure function also decreases since it has less code.
这使得将可理解和可预测的逻辑隔离到一个纯函数中成为可能。由于不纯函数的代码减少,其复杂性也降低。

5. Conclusion 5. 结论

A function is pure when given the same arguments it always returns the same value and makes no side effects.
一个函数是纯粹的,当给定相同的参数时,它总是返回相同的值,并且没有副作用。

Pure functions are easy to understand, easy to test, and can be composed and memoized. Whenever possible, strive to create pure functions.
纯函数易于理解,易于测试,并且可以组合和记忆。尽可能努力创建纯函数。

Impure functions, on the other side, are functions that access external state or produce side effects. Impure functions let your application communicate with the external world.
不纯函数则是访问外部状态或产生副作用的函数。不纯函数使您的应用程序能够与外部世界进行通信。

What other benefits of pure functions do you know?
你知道纯函数还有哪些其他好处吗?

Like the post? Please share!
喜欢这篇文章吗?请分享!

Dmitri Pavlutin

About Dmitri Pavlutin

Software developer and sometimes writer. My daily routine consists of (but not limited to) drinking coffee, coding, writing, overcoming boredom 😉. Living in the sunny Barcelona. 🇪🇸