Functional programming has been making quite a splash in the development world these days. And for good reason: Functional techniques can help you write more declarative code that is easier to understand at a glance, refactor, and test. Show
One of the cornerstones of functional programming is its special use of lists and list operations. Those things are exactly what they sound like: arrays of things and the stuff you do to them. But the functional mindset treats them a bit differently than you might expect. How to Use Map, Filter, & Reduce in JavaScript This article will take a close look at what I like to call the "big three" list operations: 326, 327, and 328. Wrapping your head around these three functions is an important step towards being able to write clean, functional code, and it opens the doors to the vastly powerful techniques of functional and reactive programming. Curious? Let's dive in. A Map From List to ListOften, we find ourselves needing to take an array and modify every element in it in exactly the same way. Typical examples of this are squaring every element in an array of numbers, retrieving the name from a list of users, or running a regex against an array of strings. 326 is a method built to do exactly that. It's defined on 330, so you can call it on any array, and it accepts a callback as its first argument. The syntax for 326 is shown below. 1 let newArray = arr.map(callback(currentValue[, index[, array]]) { 2 // return element for newArray, after executing something
3 }[, thisArg]); When you call 326 on an array, it executes that callback on every element within it, returning a new array with all of the values that the callback returned. Under the hood, 326 passes three arguments to your callback:
Let's look at some code. 326 in PracticeSuppose we have an app that maintains an array of your tasks for the day. Each 336 is an object, each with a 337 and 338 property: 1 // Durations are in minutes
2 const tasks = [ 3 let newArray = arr.map(callback(currentValue[, index[, array]]) {1 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 let newArray = arr.map(callback(currentValue[, index[, array]]) {3 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 let newArray = arr.map(callback(currentValue[, index[, array]]) {5 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 let newArray = arr.map(callback(currentValue[, index[, array]]) {7 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 let newArray = arr.map(callback(currentValue[, index[, array]]) {1 20 21 22 23 24 let newArray = arr.map(callback(currentValue[, index[, array]]) {7 26 let newArray = arr.map(callback(currentValue[, index[, array]]) {1 28 29 // return element for newArray, after executing something
0 // return element for newArray, after executing something
1 // return element for newArray, after executing something
2 // return element for newArray, after executing something
3 // return element for newArray, after executing something
4 // return element for newArray, after executing something
5Let's say we want to create a new array with just the name of each task, so we can take a look at everything we've done today. Using a 339 loop, we'd write something like this: 1 // return element for newArray, after executing something
72 // return element for newArray, after executing something
93 31 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 33 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 35 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 38 JavaScript also offers a 340 loop. It functions like a 339 loop, but manages all the messiness of checking our loop index against the array length for us: 1 // return element for newArray, after executing something
72 // return element for newArray, after executing something
93 }[, thisArg]);4 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 }[, thisArg]);6 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 }[, thisArg]);8 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 38 Using 326, we can simply write: 1 13 2 15 3 }[, thisArg]);8 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 // Durations are in minutes
0Here I included the 343 and 344 parameters to remind you that they're there if you need them. Since I didn't use them here, though, you could leave them out, and the code would run just fine. An even more succinct way of writing 326 in modern JavaScript is with arrow functions. 1 // Durations are in minutes
22 3 // Durations are in minutes
5Arrow functions are a short form for one-line functions that just have a 346 statement. It doesn't get much more readable than that. There are a few important differences between the different approaches:
Turns out, all of the functions we'll look at today share these characteristics. The fact that we don't have to manually manage the state of the loop makes our code simpler and more maintainable. The fact that we can operate directly on the element instead of having to index into the array makes things more readable. Using a 340 loop solves both of these problems for us. But 326 still has at least two distinct advantages:
Keeping the number of places where you modify state to an absolute minimum is an important tenet of functional programming. It makes for safer and more intelligible code. GotchasThe callback you pass to 326 must have an explicit 346 statement, or 326 will spit out an array full of 353. It's not hard to remember to include a 346 value, but it's not hard to forget. If you do forget, 326 won't complain. Instead, it'll quietly hand back an array full of nothing. Silent errors like that can be surprisingly hard to debug. Fortunately, this is the only gotcha with 326. But it's a common enough pitfall that I'm obliged to emphasize: Always make sure your callback contains a 346 statement! ImplementationReading implementations is an important part of understanding. So let's write our own lightweight 326 to better understand what's going on under the hood. If you want to see a production-quality implementation, check out Mozilla's polyfill at MDN. 1 // Durations are in minutes
72 // Durations are in minutes
93 // return element for newArray, after executing something
9let newArray = arr.map(callback(currentValue[, index[, array]]) {2 23 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 25 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 27 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 // return element for newArray, after executing something
920 const tasks = [1 22 const tasks = [3 This code accepts an array and a callback function as arguments. It then creates a new array, executes the callback on each element on the array we passed in, pushes the results into the new array, and returns the new array. If you run this in your console, you'll get the same result as before. While we're using a for loop under the hood, wrapping it up into a function hides the details and lets us work with the abstraction instead. That makes our code more declarative—it says what to do, not how to do it. You'll appreciate how much more readable, maintainable, and, erm, debuggable this can make your code. Filter Out the NoiseThe next of our array operations is 327. It does exactly what it sounds like: It takes an array and filters out unwanted elements. The syntax for filter is: 1 const tasks = [5 2 const tasks = [7 3 }[, thisArg]); Just like 326, 327 passes your callback three arguments:
Consider the following example, which filters out any string which is less than 8 characters. 1 let newArray = arr.map(callback(currentValue[, index[, array]]) {01 2 let newArray = arr.map(callback(currentValue[, index[, array]]) {03 3 let newArray = arr.map(callback(currentValue[, index[, array]]) {05 The expected result will be: 1 let newArray = arr.map(callback(currentValue[, index[, array]]) {07 Let's revisit our task example. Instead of pulling out the names of each task, let's say I want to get a list of just the tasks that took me two hours or more to get done. Using 340, we'd write: 1 let newArray = arr.map(callback(currentValue[, index[, array]]) {09 2 // return element for newArray, after executing something
93 }[, thisArg]);4 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 let newArray = arr.map(callback(currentValue[, index[, array]]) {15 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 let newArray = arr.map(callback(currentValue[, index[, array]]) {17 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 let newArray = arr.map(callback(currentValue[, index[, array]]) {19 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 }[, thisArg]);8 20 22 let newArray = arr.map(callback(currentValue[, index[, array]]) {24 24 26 let newArray = arr.map(callback(currentValue[, index[, array]]) {27 28 let newArray = arr.map(callback(currentValue[, index[, array]]) {29 // return element for newArray, after executing something
0let newArray = arr.map(callback(currentValue[, index[, array]]) {31 With 327, we can simply write: 1 let newArray = arr.map(callback(currentValue[, index[, array]]) {33 Just like 326, 327 lets us:
GotchasThe callback you pass to 326 has to include a return statement if you want it to function properly. With 327, you also have to include a return statement (unless you're using arrow functions), and you must make sure it returns a boolean value. If you forget your return statement, your callback will return 353, which 327 will unhelpfully coerce to 383. Instead of throwing an error, it will silently return an empty array! If you go the other route and return something that's isn't explicitly 384 or 383, then 327 will try to figure out what you meant by applying JavaScript's type coercion rules. More often than not, this is a bug. And, just like forgetting your return statement, it'll be a silent one. Always make sure your callbacks include an explicit return statement. And always make sure your callbacks in 327 return 384 or 383. Your sanity will thank you. ImplementationOnce again, the best way to understand a piece of code is... well, to write it. Let's roll our own lightweight 327. The good folks at Mozilla have an industrial-strength polyfill for you to read, too. 1 let newArray = arr.map(callback(currentValue[, index[, array]]) {35 2 // return element for newArray, after executing something
93 let newArray = arr.map(callback(currentValue[, index[, array]]) {39 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 // return element for newArray, after executing something
9let newArray = arr.map(callback(currentValue[, index[, array]]) {4 let newArray = arr.map(callback(currentValue[, index[, array]]) {43 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 let newArray = arr.map(callback(currentValue[, index[, array]]) {45 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 let newArray = arr.map(callback(currentValue[, index[, array]]) {47 20 let newArray = arr.map(callback(currentValue[, index[, array]]) {49 22 let newArray = arr.map(callback(currentValue[, index[, array]]) {51 24 // return element for newArray, after executing something
926 let newArray = arr.map(callback(currentValue[, index[, array]]) {55 28 // return element for newArray, after executing something
9 // return element for newArray, after executing something
0const tasks = [3 The Reduce MethodThe syntax for the 328 array method in JavaScript is: 1 let newArray = arr.map(callback(currentValue[, index[, array]]) {61 2 let newArray = arr.map(callback(currentValue[, index[, array]]) {63 3 let newArray = arr.map(callback(currentValue[, index[, array]]) {65 326 creates a new array by transforming every element in an array individually. 327 creates a new array by removing elements that don't belong. 328, on the other hand, takes all of the elements in an array and reduces them into a single value. Just like 326 and 327, 328 is defined on 330 and so is available on any array, and you pass a callback as its first argument. But it also takes a second argument: the value to start combining all your array elements into. 328 passes your callback four arguments:
Notice that the callback gets a previous value on each iteration. On the first iteration, there is no previous value. This is why you have the option to pass 328 an initial value: It acts as the "previous value" for the first iteration, when there otherwise wouldn't be one. Finally, bear in mind that 328 returns a single value, not an array containing a single item. This is more important than it might seem, and I'll come back to it in the examples. 328 in PracticeLet's say you want to find the sum of a list of numbers. Using a loop, it would look like this: 1 let newArray = arr.map(callback(currentValue[, index[, array]]) {67 2 let newArray = arr.map(callback(currentValue[, index[, array]]) {69 3 let newArray = arr.map(callback(currentValue[, index[, array]]) {71 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 let newArray = arr.map(callback(currentValue[, index[, array]]) {73 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 let newArray = arr.map(callback(currentValue[, index[, array]]) {75 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 }[, thisArg]);8 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 20 let newArray = arr.map(callback(currentValue[, index[, array]]) {80 While this isn't a bad use case for 340, 328 still has the advantage of allowing us to avoid mutation. With 328, we would write: 1 let newArray = arr.map(callback(currentValue[, index[, array]]) {82 2 let newArray = arr.map(callback(currentValue[, index[, array]]) {84 3 let newArray = arr.map(callback(currentValue[, index[, array]]) {86 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 let newArray = arr.map(callback(currentValue[, index[, array]]) {88 First, we call 328 on our list of numbers. We pass it a callback, which accepts the previous value and current value as arguments, and returns the result of adding them together. Since we passed }[, thisArg]);08 as a second argument to 328, it'll use that as the value of }[, thisArg]);10 on the first iteration. With arrow functions, we would write it like this: 1 let newArray = arr.map(callback(currentValue[, index[, array]]) {90 2 let newArray = arr.map(callback(currentValue[, index[, array]]) {92 If we take it step by step, it looks like this: Iteration PreviousCurrentTotal10112123333646410510515If you're not a fan of tables, run this snippet in the console: 1 let newArray = arr.map(callback(currentValue[, index[, array]]) {94 2 let newArray = arr.map(callback(currentValue[, index[, array]]) {96 3 let newArray = arr.map(callback(currentValue[, index[, array]]) {98 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 200 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 202 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 204 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 let newArray = arr.map(callback(currentValue[, index[, array]]) {86 20 // return element for newArray, after executing something
922 210 24 // return element for newArray, after executing something
9To recap: 328 iterates over all the elements of an array, combining them however you specify in your callback. On every iteration, your callback has access to the previous value, which is the total-so-far, or accumulated value; the current value; the current index; and the entire array, if you need them. Let's turn back to our tasks example. We've gotten a list of task names from 326, and a filtered list of tasks that took a long time with... well, 327. What if we wanted to know the total amount of time we spent working today? Using a 340 loop, you'd write: 1 214 2 let newArray = arr.map(callback(currentValue[, index[, array]]) {71 3 }[, thisArg]);4 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 220 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 222 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 224 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 }[, thisArg]);8 20 // return element for newArray, after executing something
922 230 24 // return element for newArray, after executing something
926 234 With 328, that becomes: 1 236 2 238 That's almost all there is to it. Almost, because JavaScript provides us with one more little-known method, called }[, thisArg]);16. In the examples above, 328 started at the first item in the array, iterating from left to right: 1 240 2 242 3 244 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 }[, thisArg]);8 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 248 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 250 }[, thisArg]);16 does the same thing, but in the opposite direction: 1 240 2 254 3 244 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 }[, thisArg]);8 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 // return element for newArray, after executing something
9let newArray = arr.map(callback(currentValue[, index[, array]]) {6 262 I use 328 every day, but I've never needed }[, thisArg]);16. I reckon you probably won't, either. But in the event you ever do, now you know it's there. GotchasThe three big gotchas with 328 are:
Fortunately, the first two are easy to avoid. Deciding what your initial value should be depends on what you're doing, but you'll get the hang of it quickly. The last one might seem a bit strange. If 328 only ever returns a single value, why would you expect an array? There are a few good reasons for that. First, 328 always returns a single value, not always a single number. If you reduce an array of arrays, for instance, it will return a single array. If you're in the habit of reducing arrays, it would be fair to expect that an array containing a single item wouldn't be a special case. Second, if 328 did return an array with a single value, it would naturally play nice with 326 and 327, and other functions on arrays that you're likely to be using with it. ImplementationTime for our last look under the hood. As usual, Mozilla has a bulletproof polyfill for reduce if you want to check it out. 1 264 2 266 3 268 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 270 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 272 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 let newArray = arr.map(callback(currentValue[, index[, array]]) {51 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 268 20 278 22 const tasks = [3 Two things to note here:
Putting It Together: Map, Filter, Reduce, and ChainabilityAt this point, you might not be that impressed. Fair enough: 326, 327, and 328, on their own, aren't awfully interesting. After all, their true power lies in their chainability. Let's say I want to do the following:
First, let's define our tasks for Monday and Tuesday: 1 282 2 284 3 286 let newArray = arr.map(callback(currentValue[, index[, array]]) {2 288 let newArray = arr.map(callback(currentValue[, index[, array]]) {4 290 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 284 let newArray = arr.map(callback(currentValue[, index[, array]]) {8 294 20 296 22 298 24 // return element for newArray, after executing something
0026 // return element for newArray, after executing something
928 // return element for newArray, after executing something
04 // return element for newArray, after executing something
0284 // return element for newArray, after executing something
2 // return element for newArray, after executing something
08 // return element for newArray, after executing something
4 // return element for newArray, after executing something
10 // return element for newArray, after executing something
11290 // return element for newArray, after executing something
13284 // return element for newArray, after executing something
15 // return element for newArray, after executing something
16 // return element for newArray, after executing something
17288 // return element for newArray, after executing something
19290 // return element for newArray, after executing something
21284 // return element for newArray, after executing something
23 // return element for newArray, after executing something
24 // return element for newArray, after executing something
25 // return element for newArray, after executing something
26 // return element for newArray, after executing something
27298 // return element for newArray, after executing something
29 // return element for newArray, after executing something
00 // return element for newArray, after executing something
31 // return element for newArray, after executing something
32 // return element for newArray, after executing something
33 // return element for newArray, after executing something
34And now, our lovely-looking transformation: 1 // return element for newArray, after executing something
362 // return element for newArray, after executing something
383 // return element for newArray, after executing something
40let newArray = arr.map(callback(currentValue[, index[, array]]) {2 // return element for newArray, after executing something
42let newArray = arr.map(callback(currentValue[, index[, array]]) {4 // return element for newArray, after executing something
44let newArray = arr.map(callback(currentValue[, index[, array]]) {6 // return element for newArray, after executing something
46let newArray = arr.map(callback(currentValue[, index[, array]]) {8 // return element for newArray, after executing something
4820 // return element for newArray, after executing something
5022 // return element for newArray, after executing something
5224 // return element for newArray, after executing something
5426 // return element for newArray, after executing something
5628 // return element for newArray, after executing something
58 // return element for newArray, after executing something
0 // return element for newArray, after executing something
60 // return element for newArray, after executing something
2 // return element for newArray, after executing something
62 // return element for newArray, after executing something
4 // return element for newArray, after executing something
64 // return element for newArray, after executing something
11 // return element for newArray, after executing something
66If you've made it this far, this should be pretty straightforward. There are two bits of weirdness to explain, though. First, on line 10, I have to write: 1 // return element for newArray, after executing something
682 // return element for newArray, after executing something
703 // return element for newArray, after executing something
72let newArray = arr.map(callback(currentValue[, index[, array]]) {2 // return element for newArray, after executing something
74Two things to explain here:
The second bit that might make you a bit uncomfortable is the last 328, namely: 1 // return element for newArray, after executing something
682 // return element for newArray, after executing something
783 // return element for newArray, after executing something
80let newArray = arr.map(callback(currentValue[, index[, array]]) {2 // return element for newArray, after executing something
82let newArray = arr.map(callback(currentValue[, index[, array]]) {4 // return element for newArray, after executing something
84let newArray = arr.map(callback(currentValue[, index[, array]]) {6 }[, thisArg]);8 That call to 326 returns an array containing a single value. Here, we call 328 to pull out that value. Finally, let's see how our friend the 340 loop would get it done: 1 // return element for newArray, after executing something
882 // return element for newArray, after executing something
903 // return element for newArray, after executing something
92let newArray = arr.map(callback(currentValue[, index[, array]]) {2 // return element for newArray, after executing something
94let newArray = arr.map(callback(currentValue[, index[, array]]) {4 248 let newArray = arr.map(callback(currentValue[, index[, array]]) {6 // return element for newArray, after executing something
98let newArray = arr.map(callback(currentValue[, index[, array]]) {8 300 20 let newArray = arr.map(callback(currentValue[, index[, array]]) {71 22 304 24 306 26 let newArray = arr.map(callback(currentValue[, index[, array]]) {19 28 }[, thisArg]);8 // return element for newArray, after executing something
0248 // return element for newArray, after executing something
2314 // return element for newArray, after executing something
4316 // return element for newArray, after executing something
11}[, thisArg]);8 // return element for newArray, after executing something
13 // return element for newArray, after executing something
15 // return element for newArray, after executing something
17 // return element for newArray, after executing something
19323 // return element for newArray, after executing something
21 // return element for newArray, after executing something
9Tolerable, but noisy. Conclusion and Next StepsIn this tutorial, you've learned how 326, 327, and 328 work; how to use them; and roughly how they're implemented. You've seen that they all allow you to avoid mutating state, which using 339 and 340 loops requires, and you should now have a good idea of how to chain them all together. By now, I'm sure you're eager for practice and further reading. For a masterclass in functional programming in JavaScript, check out our online course. Bagaimana cara menjalankan JavaScript Coba Anda jelaskan?Mengaktifkan JavaScript di browser Anda. Buka Chrome di komputer Anda.. Klik. Setelan.. Klik Keamanan dan Privasi.. Klik Setelan situs.. Klik JavaScript.. Pilih Situs dapat menggunakan JavaScript.. Langkah kerja JavaScript?Cara Kerja JavaScript pada Website
JavaScript di-embed langsung ke halaman web atau direferensikan melalui file .js terpisah. Saat pengunjung membuka halaman web, browser akan menjalankan skrip beserta kode HTML dan CSS untuk membuat halaman fungsional yang disajikan melalui tab browser.
Bagaimana cara yang benar untuk menggunakan file JavaScript di HTML?Anda bisa langsung menambahkan JavaScript di HTML dengan menggunakan tag <script></script> yang mencakup semua kode JS yang Anda tulis. Kode JS yang bisa ditambahkan: di antara tag <head> di antara <body>
Apakah JavaScript cocok untuk pemula?JavaScript adalah salah satu bahasa pemrograman populer saat ini. Javascript ini punya banyak keunggulan yang membuatnya cocok untuk pemula. Untuk itu, belajar JavaScript sangat disarankan jika Anda ingin membuat website.
|