Functional programming in JavaScript is all the rage these days.
如今,JavaScript中的函数式编程风靡一时。
And it should be: The benefits for readability, maintainability, and testability are dramatic.
应该是:可读性,可维护性和可测试性的好处是巨大的。
"Functional programmming" means many things to many people. One of my favorite tools from the FP mindset is programming in a list-processing style. This entails taking arrays—or lists, as I prefer to call them—as your fundamental data structure.
“功能编程”对许多人来说意味着很多事情。 FP思维方式中我最喜欢的工具之一是以列表处理方式进行编程。 这需要将数组(或我更喜欢称其为列表)的数据作为基本数据结构。
Then, your program becomes a series of operations on elements in the list. And there are many contexts in which this is useful: Mapping AJAX results to React components with map
. Removing extraneous data with filter
. Doing...Well, fancy things with reduce
.
然后,您的程序将对列表中的元素进行一系列操作。 在许多情况下这很有用: 使用map
AJAX结果映射到React组件 。 用filter
删除无关的数据。 做...好吧,用reduce
看中事物。
These functions, called "Array Extras", are abstractions over for
loops. There is nothing you can do with with these functions that you can't achieve with for
, and vice-versa.
这些功能称为“数组额外功能”,是for
循环的抽象。 这些功能对for
无法实现for
,反之亦然。
There are some finer points as to their usage, though, which are important to keep in mind as you cultivate an increasingly functional mindsetn.
但是,关于它们的用法,还有一些要点,在培养功能日益强大的思维方式时,请记住这些要点。
Today, we'll lay some ground work by taking a look at all three of these functions. We might even meet an extra function or two, just for good measure.
今天,我们将通过研究所有这三个功能来奠定基础。 出于良好的考虑,我们甚至可能会遇到一两个额外的功能。
But, we'll never figure out what's in store if we don't get started. So let's get started with something you already know: The good ol' for
loop.
但是,如果我们不入门,我们永远也不会弄清楚店里有什么。 因此,让我们开始使用的东西你已经知道:好醇” for
循环。
与for的迭代 ( Iteration with for )
We use for
loops to iterate over every item in an array. Usually, we do something to each item along the way.
我们使用for
循环来遍历数组中的每个项目。 通常,我们会在此过程中对每个项目进行处理。
One simple example would be capitalizing every string in an array.
一个简单的示例将大写数组中的每个字符串。
const strings = ['arielle', 'are', 'you', 'there'];
const capitalizedStrings = [];
for (let i = 0; i < strings.length; i += 1) {
const string = strings[i];
capitalizedStrings.push(string.toUpperCase());
}
console.log(capitalizedStrings);
In this snippet, we start a list of lowercase names. Then, we initialize an empty array, in which we'll store our capitalized strings.
在此代码段中,我们开始列出一个小写名称。 然后,我们初始化一个空数组,在其中存储大写的字符串。
Inside of the for
loop, we simply grab the next string on every iteration; capitalize it; and push it into capitalizedStrings
. At the end of the loop, capitalizedStrings
contains every word in strings
, but...You know, capitalized.
在for
循环内部,我们只需在每次迭代中获取下一个字符串即可; 大写; 并将其推送到capitalizedStrings
。 在循环的最后, capitalizedStrings
strings
包含strings
中的每个单词,但是...大写。
This brings us to our first function: forEach. This is a method on arrays that "automatically" loops through the list for us. In other words, it handles the details of initializing and incrementing a counter for us.
这将我们带到我们的第一个功能: forEach 。 这是对阵列的一种方法,可为我们“自动”遍历列表。 换句话说,它为我们处理初始化和递增计数器的细节。
Instead of the above, where we manually index into strings
, we can simply call forEach
, and receive the next string on every iteration. The updated version would look something like:
代替上面的方法,我们在其中手动索引为strings
,我们可以简单地调用forEach
,并在每次迭代时接收下一个字符串。 更新后的版本如下所示:
const strings = ['arielle', 'are', 'you', 'there'];
const capitalizedStrings = [];
strings.forEach(function (string) {
capitalizedStrings.push(string.toUpperCase());
})
...This is almost the same as what we started with. But getting rid of that i
is already a big win.
...这几乎与我们开始时一样。 但是摆脱这一点i
已经是一个巨大的胜利。
This also introduces a major pattern we'll see time and time again. Namely: We prefer to use methods on Array.prototype
that abstract away details like initializing and incrementing counters. That way, we can focus on the important logic, rather than the boilerplate.
这也引入了一种主要模式,我们将一次又一次看到。 即:我们更喜欢在Array.prototype
上使用抽象化细节的方法,例如初始化和递增计数器。 这样,我们可以专注于重要的逻辑,而不是样板。
But first! A message from our sponsor.
但首先! 来自赞助商的信息。
凯撒,以蛮力闻名 ( Caesar, Known for Brute Force )
In the snippets below, we'll use encrypting and decrypting strings in our examples of map
, reduce
, and filter
.
在下面的代码片段中,我们将在map
, reduce
和filter
示例中使用加密和解密字符串。
For example:
例如:
// Thanks to EvanHahn for this: https://gist.github.com/EvanHahn/2587465
const caesar = require('./caesar');
// We can encrypt the string: 'this is my secret message' with `caesar`
// Here, we scramble the message by shifting each letter forward by 2 letters: 'a' becomes 'c'; 's' becomes 'u'; etc.
const encryptedString = caesar('this is my super secret message.', 2);
console.log(encryptedString); // 'vjku ku oa uwrgt ugetgv uvtkpi.'
The idea is that, if I send you a normal message, like 'this is my super secret message'
, and someone else gets their hands on it, they can read the secret immediately. This obviously sucks if you're sending sensitive information, like passwords, which someone might be listening for.
这个想法是,如果我向您发送一条普通消息,例如'this is my super secret message'
,而其他人得到了帮助,他们可以立即读取该机密。 如果您要发送敏感信息(例如密码),而有人正在听这些信息,这显然很糟糕。
...And, yes. Someone is always listening.
...是的。 有人 总是在听。
Encrypting a string means: "scrambling it to make it hard to read without unscrambling." This way, even if someone is listening, and even if they do intercept your message, it will remain unreadable until they unscramble it. To quote the example above, it's pretty non-obvious what the string 'vjku ku oa uwrgt ugetgv uvtkpi.'
is supposed to mean.
加密字符串的含义是:“对其进行加扰使其难以阅读而不加扰动。” 这样,即使有人在听,并且即使他们确实拦截了您的消息,消息也将保持不可读状态,直到他们对其进行解密。 引用上面的示例,字符串'vjku ku oa uwrgt ugetgv uvtkpi.'
应该是这个意思。
The Caesar cipher is one of the simplest ways to scramble a string like this. To encrypt with the Caesar cipher, we pick a key, n
, between 1 and 24, and replace every letter in the original string with the one n
letters further down the alphabet.
凯撒密码是扰乱此类字符串的最简单方法之一。 要使用Caesar密码进行加密,我们选择一个介于1和24之间的密钥 n
,并将原始字符串中的每个字母替换为字母表后面的n
个字母。
So, if we choose the key 2, a
becomes c
; b
becomes d
; c
becomes e
; etc.
因此,如果我们选择键2,则a
变为c
; b
变成d
; c
变成e
; 等等
Substituting letters like this makes the original string pretty unreadable. But, we scrambled by simply shifting letters. So, all you need to do to unscramble is shift them back. If you get a message you know was encrypted with the key 2, all you need to do to decrypt is shift letters back two spaces. So, c
becomes a
; d
becomes b
; etc.
像这样替换字母会使原始字符串变得难以阅读。 但是,我们只是通过移动字母来进行加扰。 因此,您要做的所有工作就是将它们移回原位。 如果收到一条消息,您知道该密钥已使用密钥2加密,则解密所需要做的就是将字母向后移两个空格。 因此, c
变为a
; d
变成b
; 等等
Unfortunately, this is a pretty useless form of encryption nowadays. It's extremely easy to break. An easy way to decrypt any string encrypted with a Caesar cipher is to just try to decrypt it with every possible key. One of the results will be correct. All you need to do is scan the list of 24 outputs for the one that's English. The others will be just as unreadable as the original string.
不幸的是,如今这是一种非常无用的加密形式。 破解起来非常容易。 解密使用Caesar密码加密的任何字符串的一种简单方法是尝试使用所有可能的密钥对其进行解密。 其中一个结果是正确的。 您需要做的就是扫描24个输出列表中的英文输出。 其他的将与原始字符串一样不可读。
Below, I'll use a function called tryAll
, which does just that. Namely: It takes an encrypted string, and returns a list of every possible decryption. One of those results will be the string we want. So, this always breaks the cipher.
在下面,我将使用一个名为tryAll
的函数来执行此操作。 即:它接受一个加密的字符串,并返回所有可能解密的列表。 这些结果之一将是我们想要的字符串。 因此,这总是会破坏密码。
Of course, scanning a list of 24 possible decryptions...Kind of sucks. It sort of feels like we should be able to automatically throw away the ones that are obviously wrong.
当然,扫描24种可能的解密列表……很烂。 感觉就像我们应该能够自动丢弃那些明显错误的内容。
In fact, it's easy to do that. In the section on filter
, you'll see a function, called isEnglish
, which does just this. In particular, it reads a string; counts how many of the most common 1,000 words in English occur in that string; and, if it finds more than 3 of those words in the sentence, it classifies the string as English. If the string contains fewer than 3 words from that list, it throws it out as "junk".
实际上,这样做很容易。 在filter
的部分中,您将看到一个名为isEnglish
的函数,该函数就是这样做的。 特别是,它读取一个字符串。 计算该字符串中英语中最常见的1,000个单词中有多少个; 并且,如果在句子中找到三个以上的单词,则将该字符串分类为英语。 如果该字符串包含少于3个单词,则将其丢弃为“垃圾”。
There are more sophisticated ways to check if a string is English, of course, but this is fine for now.
当然,还有更复杂的方法来检查字符串是否为英语,但这目前还可以。
The implementations of tryAll
and isEnglish
aren't important, but if you're curious, you can find them at this gist: tryAll / isEnglish.
tryAll
和isEnglish
的实现并不重要,但是如果您好奇,可以在以下要点找到它们: tryAll / isEnglish 。
使用地图转换列表 ( Transforming Lists with map )
Refactoring a for
loop to use forEach
hints at advantages of this style. But there's still room for improvement in the updated version.
重构for
循环以使用forEach
暗示这种样式的优点。 但是更新版本仍有改进的空间。
In the example above, we're updating capitalizedStrings
within the callback to forEach
. There's nothing inherently wrong with this. But, it saves a lot of headache to avoid side effects like that whenever possible.
在上面的示例中,我们将回调内的capitalizedStrings
更新为forEach
。 这天生没有错。 但是 ,它避免了很多可能的副作用,从而避免了很多麻烦。
If we don't have to update data structures that live in a different scope...We probably shouldn't.
如果我们没有生活在不同的范围内更新数据结构......我们也许不应该。
In this particular case, we wanted to turn every string in strings
into its capitalized version. This is a very common use case for a for
loop: Take everything in a list; turn it into something else; and collect the results in a new list.
在这种特殊情况下,我们希望把每串strings
到它的大写版本。 这是for
循环的非常常见的用例:将所有内容放在列表中; 把它变成其他东西; 并将结果收集到新列表中。
Transforming every element in a list into a new one and collecting the results is called mapping. JavaScript has a built-in function for just this use case, called, appropriately enough, map.
将列表中的每个元素转换为新元素并收集结果称为映射 。 JavaScript为此用途提供了一个内置函数,即适当地称为map 。
We used forEach
because it abstracts away the need to manage the iteration variable, i
. We don't have to manually index into the array, etc., and so we can focus on the logic that really matters.
之所以使用forEach
是因为它抽象出了管理迭代变量i
。 我们不必手动索引到数组等中,因此我们可以专注于真正重要的逻辑。
Similarly, we use map
because it abstracts away initializing an empty array, and pushing to it. Just like forEach
accepts a callback that does something with each string value, map
accepts a callback that does something with each string value.
类似地,我们使用map
因为它抽象了初始化一个空数组并将其推送到它的过程。 就像forEach
接受对每个字符串值执行某些操作的回调一样, map
接受对每个字符串值执行某些操作的回调。
Let's look at a quick demo before a final explanation. In this example, I'm using a function to "encrypt" a list of messages. We could use a for
loop, and we could use forEach
...But, it's better with map
.
在进行最终解释之前,让我们看一下快速演示。 在此示例中,我使用一个函数来“加密”消息列表。 我们可以使用for
循环,也可以使用forEach
...但是,最好使用map
。
const caesar = require('../caesar');
const key = 12;
const messages = [
'arielle, are you there?',
'the ghost has killed the shell',
'the liziorati attack at dawn'
]
const encryptedMessages = messages.map(function (string) {
return caesar(string, key);
})
console.log(encryptedMessages);
Note what happened here.
注意这里发生了什么。
- We start with a list of
messages
, in plain English. 我们从简单的英文messages
列表开始。 - We use the
map
method onmessages
to encrypt each string with thecaesar
function, and automagically store the result in a new array. 我们对messages
使用map
方法,通过caesar
函数对每个字符串进行加密,然后自动将结果存储在新数组中。
After the above code runs, encryptedMessages
looks like: ['mduqxxq, mdq kag ftqdq?', 'ftq staef tme wuxxqp ftq etqxx', 'ftq xuluadmfu mffmow mf pmiz']
.
上面的代码运行之后, encryptedMessages
看起来像: ['mduqxxq, mdq kag ftqdq?', 'ftq staef tme wuxxqp ftq etqxx', 'ftq xuluadmfu mffmow mf pmiz']
。
This is a much higher level of abstraction than manually pushing to a list.
这比手动推送到列表要高得多。
Now is a good time to point out that we can use arrow functions to express this sort of thing more concisely:
现在是指出我们可以使用箭头函数更简洁地表达这种事情的好时机:
const caesar = require('../caesar');
const key = 12;
const messages = [
'arielle, are you there?',
'the ghost has killed the shell',
'the liziorati attack at dawn'
];
const encryptedMessages = messages.map(string => string.toUpperCase()):
console.log(encryptedMessages);
Even better.
更好 。
This is generally how I prefer to write, but I'll stick with function
syntax for the sake of clarity.
通常,这是我更喜欢写的方式,但是为了清楚起见,我将坚持使用function
语法。
用过滤器扔东西 ( Throwing Things Away with filter )
Another common pattern is using a for
loop to process items in a list, but only pushing/preserving some of them.
另一个常见的模式是使用for
循环来处理列表中的项目,但仅推送/保留其中的一些项目。
We usually decide which items to keep, and which to throw away, by doing an if
check.
我们通常通过进行if
检查来决定保留哪些物品,丢弃哪些物品。
In raw JS, this might look like:
在原始JS中,这可能类似于:
// Secret message! This was a string encrypted with a key between 1 and 24.
const encryptedMessage = 'mduqxxq, mdq kag ftqdq?'];
// We can break this code by just decrypting with _every_ possible key.
const possibilities = tryAll(encryptedMessage);
// Most of these decryption attempts aren't readable. Sad.
// We can speed up finding the ones that are probably junk with an if check
const likelyPossibilities = [];
possibilities.forEach(function (decryptionAttempt) {
// Keep the string if it looks like an English sentence
if (isEnglish(decryptionAttempt)) {
likelyPossibilities.push(decryptionAttempt);
}
})
First, we try to decrypt the encrypted message with every possible key. That means we end up with an array of 24 possibilities.
首先,我们尝试使用所有可能的密钥解密加密的消息。 这意味着我们最终有24种可能性。
In this loop, we test each one to see if it looks like an English string. If so, we keep it. If not, we throw it away.
在此循环中,我们测试每个人是否看起来像英语字符串。 如果是这样,我们保留它。 如果没有,我们将其丢弃。
This is clearly a common use case. So, there's a built-in for it, aptly named filter.
显然,这是一个常见的用例。 因此,有一个内置的名称,恰当地称为filter 。
As with map
, we pass filter
a callback, which also gets each string. The difference is that, filter
will only save items in an array if the callback returns true. So, we could express the above as:
与map
,我们通过filter
回调,该回调也获取每个字符串。 区别在于,仅当回调返回true时, filter
才会将项目保存在数组中。 因此,我们可以将以上内容表达为:
// Secret message! This was a string encrypted with a key between 1 and 24.
const encryptedMessage = 'mduqxxq, mdq kag ftqdq?'];
// We can break this code by just decrypting with _every_ possible key.
const possibilities = tryAll(encryptedMessage);
// Most of these decryption attempts aren't readable. Sad.
// We can speed up finding the ones that are probably junk with an if check
const likelyPossibilities = possibilities.filter(function (string) {
// If isEnglish(string) returns true, filter saves in the output array
return isEnglish(string);
})
Since this callback just calls isEnglish
, we could actually write it even more concisely, like this:
由于此回调仅调用isEnglish
,因此实际上我们可以更简洁地编写它,如下所示:
// Secret message! This was a string encrypted with a key between 1 and 24.
const encryptedMessage = 'mduqxxq, mdq kag ftqdq?'];
// We can break this code by just decrypting with _every_ possible key.
const possibilities = tryAll(encryptedMessage);
// Most of these decryption attempts aren't readable. Sad.
// We can speed up finding the ones that are probably junk with an if check
const likelyPossibilities = possibilities.filter(isEnglish);
...Bang.
...砰。
减少负担 ( Bringing Things Together with reduce )
So far, we've seen abstractions for three common use cases around arrays:
到目前为止,我们已经看到了数组周围三个常见用例的抽象:
forEach
, which allows us to iterate over a list as withfor
, but eliminates the need to manually manage the iteration variablei
; index into the list; etc.forEach
,它允许我们像for
一样遍历一个列表,但是不需要手动管理迭代变量i
; 索引到列表; 等等map
, which lets us transform each element of a list, and collect the results in an arraymap
,它使我们可以转换列表的每个元素,并将结果收集到数组中filter
, which lets us choose which elements of a list to keepfilter
,它使我们可以选择保留列表中的哪些元素
Another common use case is: Iterating over a list to collect its elements into a single result.
另一个常见的用例是:遍历列表以将其元素收集到单个结果中。
The prototypical example is adding up a bunch of numbers.
典型的例子是将一堆数字相加。
// prices of: (big) box of oreos, girl scout cookies, fidget spinner, gold-plated Arch linux magnet
const prices = [12, 19, 7, 209];
// variable to store our total prices in
let totalPrice = 0;
for (let i = 0; i < prices.length; i += 1) {
totalPrice += prices[i];
}
// Report our total prices: $247
console.log(`Your total is ${totalPrice}.`);
As I'm sure you'd guess, there's an abstraction for this, too: reduce.
就像您确定的那样,我也对此有一个抽象: reduce 。
Refactoring the above loop with reduce
looks like:
用reduce
重构上述循环看起来像:
const prices = [12, 19, 7, 209];
prices.reduce(function (totalPrice, nextPrice) {
// totalPrice is the price so far
console.log(`Total price so far: ${totalPrice}`)
console.log(`Next price to add: ${nextPrice}`)
// update totalPrice by adding the next price
totalPrice += nextPrice
// return the total price, and start over again
return totalPrice
}, 0)
// ^ the second argument to `reduce` is the totalPrice to start with on the first iteration
Like map
and filter
, reduce
accepts a callback, which it runs on each element of the array.
像map
和filter
一样, reduce
接受一个回调,该回调在数组的每个元素上运行。
Unlike map
and filter
, the callback we pass to reduce
accepts two arguments: a totalPrice
, and a nextPrice
.
与map
和filter
不同,我们传递给reduce
的回调接受两个参数: totalPrice
和nextPrice
。
totalPrice
is like total
in the first example: It's the price we've gotten by adding up all the prices we've seen so far.
totalPrice
就像第一个示例中的total
:这是我们通过将到目前为止所看到的所有价格相加得到的价格。
nextPrice
is the number we got by doing prices[i]
in the first example. Recall that map
and reduce
automatically index into the array for us, and pass this value to their callbacks automatically. reduce
does the same thing, but passes that value as the second argument to its callback.
nextPrice
是第一个示例中通过prices[i]
获得的数字。 回忆一下该map
并为我们自动reduce
到数组的索引,然后将此值自动传递给其回调。 reduce
做同样的事情,但是将该值作为第二个参数传递给其回调。
Just like with map
and reduce
, on each iteration, we have to return the value we're interested in getting back at the end of the loop. Namely, totalPrice
.
就像map
和reduce
,在每次迭代中,我们都必须在循环结束时返回我们想要返回的值。 即totalPrice
。
Finally, note that we pass another argument after the callback to reduce
. In this case, we pass 0
. This is analogous to the line in the first example where we set const total = 0
. That second argument is the initial value of totalPrice
.
最后,请注意,我们在回调后传递另一个参数reduce
。 在这种情况下,我们传递0
。 这类似于我们将const total = 0
设置为第一个示例中的行。 第二个参数是totalPrice
的初始值。
reduce
is harder to grok than map
or filter
, so let's take a look at another example.
与map
或filter
, reduce
更难掌握,因此让我们来看另一个示例。
In the previous example, we used reduce
to collect an array of numbers into a sum. But reduce
is versatile. We can use it to turn a list into a any single result.
在前面的示例中,我们使用reduce
来将一个数字数组收集为一个和。 但是reduce
是通用的。 我们可以使用它将列表转换为任何单个结果。
For example, we can use it to build up a string.
例如,我们可以使用它来构建一个字符串。
const courses = ['Introduction to Programming', 'Algorithms & Data Structures', 'Discrete Math'];
const curriculum = courses.reduce(function (courseList, course) {
// add the name to the class, with a preceding newline and tab for indentation
return courseList += `\n\t${course}`;
}, 'The Computer Science curriculum consists of:');
console.log(curriculum);
This generates the output:
生成输出:
The Computer Science curriculum consistsof:
Introduction to Programming
Algorithms & Data Structures
Discrete Math
I don't see a ton of examples of using reduce
to build strings like this, for some reason, but it's common practice in a functional style.
由于某种原因,我没有看到很多使用reduce
来构建这样的字符串的示例,但这是功能样式的常见做法。
Again, reduce
is versatile: We can use it to turn a list into any kind of "single" result.
同样, reduce
是通用的:我们可以使用它将列表转换为任何类型的“单个”结果。
Even if that single result is another array.
即使单个结果是另一个数组。
Check it:
核实:
const names = ['arielle', 'jung', 'scheherazade'];
const titleCase = function (name) {
// uppercase first letter of name
const first = name[0];
const capitalizedFirst = first.toUpperCase();
// get rest of name
const rest = name.slice(1);
// create list with all letters of the name, including capitalized first letter
const letters = [capitalizedFirst].concat(rest);
// join letters, return result
return letters.join('');
}
const titleCased = names.reduce(function (titleCasedNames, name) {
// title-case the next name
const titleCasedName = titleCase(name);
// add the title-cased name to a list
titleCasedNames.push(titleCasedName);
// return list of capitalizedNames
return titleCasedNames
}, [])
// ^ start with an empty list
console.log(titleCased);
// ["Arielle", "Jung", "Scheherazade"]
So...This is neat. We used reduce
to turn a list of lower-cased names into a list of title-cased names.
所以...这很整洁。 我们使用reduce
将一个小写名称列表转换为一个标题大写名称列表。
Previously, we turned a list of numbers into a single sum, and a list of strings into a single string. Here, we turned a list of names into a single list of title-case names.
以前,我们将数字列表转换为单个和,将字符串列表转换为单个字符串。 在这里,我们将名称列表变成了标题大小写名称的单个列表。
This is still allowed, because the single list of title-cased names is still a single result. It just happens to be a collection, rather than a primitive type.
仍然允许这样做,因为标题大小写名称的单个列表仍然是单个结果。 它只是一个集合,而不是原始类型。
You might have noticed something about that last example: We used reduce
for the same task that we used map
for earlier.
您可能已经注意到了有关最后一个示例的一些信息:对于先前使用map
的相同任务,我们使用了reduce
。
It might not be obvious, yet, but—this is a big deal.
可能还不很明显,但是- 这很重要 。
You might guess that we can write map
in terms of reduce
. You'd be right.
您可能会猜想我们可以根据reduce
编写map
。 你说的没错。
const map = (list, mapFunction) => {
const output = list.reduce((transformedList, nextElement) => {
// use the mapFunction to transform the nextElement in the list
const transformedElement = mapFunction(nextElement);
// add transformedElement to our list of transformed elements
transformedList.push(transformedElement);
// return list of transformed elements
return transformedList;
}, [])
// ^ start with an empty list
return output;
}
We can get the same result as above with:
我们可以通过以下方式获得与上述相同的结果:
// congratulations...you just implemented `map` with `reduce`. badass.
const titleCased = map(names, titleCase);
console.log(titleCased);
// ["Arielle", "Jung", "Scheherazade"]
Obligatory disclaimer: If you were actually implementing a production-ready map
, you'd need to add some features to this implementation. We'll leave all that off in the interest of clarity.
强制性免责声明:如果您实际上是在实现可用于生产的map
,则需要在此实现中添加一些功能。 为了清楚起见,我们将忽略所有这些内容。
Alright—last one. If we can use reduce
to implement map
...What about filter
?
好的-最后一个。 如果我们可以使用reduce
实现map
...那么filter
呢?
const filter = (list, predicate) => {
const output = list.reduce(function (filteredElements, nextElement) {
// only add `nextElement` if it passes our test
if (predicate(nextElement)) {
filteredElements.push(nextElement);
}
// return the list of filtered elements on each iteration
return filteredElements;
}, [])
})
}
We'd use this like:
我们将这样使用:
It's alright if this one feels confusing. It's a new idea even for more experienced developers. But, it's worth the work to wrap your head around it: We'll need this little bit of insight to make sense of transduction a little bit later.
如果这让人感到困惑,那也没关系。 即使对于经验丰富的开发人员来说,这也是一个新想法。 但是,值得您全神贯注的工作是:我们需要一点点洞察力,以便稍后再进行转导。
...And, trust me, transduction alone is cool enough to be worth the work.
...而且,请相信我,仅转导就足够了,值得进行这项工作。
向前 ( Onwards )
Today, you've learned how to use map
, filter
, and reduce
to write more readable code.
今天,您已经学习了如何使用map
, filter
和reduce
编写更具可读性的代码。
Not, of course, that there's anything wrong with using for
. But raising the level of abstraction with these functions brings immediate benefits to readability, and maintainability, by corollary.
当然,使用for
不会有任何问题。 但是,通过这些功能来提高抽象级别,必然会为可读性和可维护性带来直接的好处。
Next time, we'll round out our arsenal of array methods with a couple more favorites: flatten
, concat
, and flatMap
.
下次,我们将使用更多一些收藏夹完善数组方法库: flatten
, concat
和flatMap
。
Stay tuned!
敬请关注!
翻译自: https://scotch.io/tutorials/list-processing-with-map-filter-and-reduce