diff --git a/posts/closure.md b/posts/closure.md index 8ad3ad5..bfda815 100644 --- a/posts/closure.md +++ b/posts/closure.md @@ -9,7 +9,7 @@ Closures are confusing because they are an “invisible” concept. When you use an object, a variable, or a function, you do this intentionally. You think: “I’m gonna need a variable here,” and add it to your code. -Closures are different. By the time most people approach closures, they have already used them unknowingly many times — and it is likely that this is true for yourself, too. So learning closures is less about understanding a *new* concept and more about recognizing something you have *already been doing* for a while. +Closures are different. By the time most people approach closures, they have already used them unknowingly many times — and it is likely that this is true for yourself, too. So learning closures is less about understanding a _new_ concept and more about recognizing something you have _already been doing_ for a while. ### tl;dr @@ -18,53 +18,53 @@ You have a closure when **a function accesses variables defined outside of it**. For example, this code snippet contains a closure: ```js -let users = ['Alice', 'Dan', 'Jessica']; -let query = 'A'; -let user = users.filter(user => user.startsWith(query)); +let users = ['Alice', 'Dan', 'Jessica'] +let query = 'A' +let user = users.filter((user) => user.startsWith(query)) ``` -Notice how `user => user.startsWith(query)` is itself a function. It uses the `query` variable. But the `query` variable is defined *outside* of that function. That’s a closure. +Notice how `user => user.startsWith(query)` is a function itself. It uses the "`query`" variable which is defined _outside_ of that function. That’s a closure. --- -**You can stop reading here, if you want.** The rest of this article approaches closures in a different way. Instead of explaining what a closure is, it will walk you through the process of *discovering* closures — like the first programmers did in the 1960s. +**You can stop reading here, if you want.** The rest of this article approaches closures in a different way. Instead of explaining what a closure is, it will walk you through the process of _discovering_ closures — like the first programmers did in the 1960s. --- ### Step 1: Functions Can Access Outside Variables -To understand closures, we need to be somewhat familiar with variables and functions. In this example, we declare the `food` variable *inside* the `eat` function: +To understand closures, we need to be somewhat familiar with variables and functions. In this example, we declare the `food` variable _inside_ the `eat` function: ```js function eat() { - let food = 'cheese'; - console.log(food + ' is good'); + let food = 'cheese' + console.log(food + ' is good') } -eat(); // Logs 'cheese is good' +eat() // Logs 'cheese is good' ``` -But what if we wanted to later change the `food` variable *outside* of the `eat` function? To do this, we can move the `food` variable itself out of our function into the top level: +But what if we wanted to later change the `food` variable _outside_ of the `eat` function? To do this, we can move the `food` variable itself out of our function into the top level: ```js -let food = 'cheese'; // We moved it outside +let food = 'cheese' // We moved it outside function eat() { - console.log(food + ' is good'); + console.log(food + ' is good') } ``` This lets us change the `food` “from the outside” any time that we want to: ```js -eat(); // Logs 'cheese is good' -food = 'pizza'; -eat(); // Logs 'pizza is good' -food = 'sushi'; -eat(); // Logs 'sushi is good' +eat() // Logs 'cheese is good' +food = 'pizza' +eat() // Logs 'pizza is good' +food = 'sushi' +eat() // Logs 'sushi is good' ``` -In other words, the `food` variable is no longer *local* to our `eat` function, but our `eat` function nevertheless has no trouble accessing it. **Functions can access variables outside of them.** Stop for a second and make sure that you have no problem with this idea. Once it has settled comfortably in your brain, move to the second step. +In other words, the `food` variable is no longer _local_ to our `eat` function, but our `eat` function nevertheless has no trouble accessing it. **Functions can access variables outside of them.** Stop for a second and make sure that you have no problem with this idea. Once it has settled comfortably in your brain, move to the second step. ### Step 2: Wrapping Code in a Function Call @@ -98,20 +98,20 @@ function doTheThing() { /* A snippet of code */ } -doTheThing(); -doTheThing(); +doTheThing() +doTheThing() ``` Using a function gives us the ultimate flexibility because we can run this function any number of times, at any time — and from anywhere in our program. -In fact, **we can even call our new function only *once***, if we wanted to: +In fact, **we can even call our new function only _once_**, if we wanted to: ```js function doTheThing() { /* A snippet of code */ } -doTheThing(); +doTheThing() ``` Notice how the code above is equivalent to the original code snippet: @@ -126,59 +126,59 @@ In other words, **if we take some piece of code, “wrap” that code in a funct We have traced our way through two different ideas: -* **Functions can access variables defined outside of them.** -* **Wrapping code in a function and calling it once doesn’t change the result.** +- **Functions can access variables defined outside of them.** +- **Wrapping code in a function and calling it once doesn’t change the result.** Now let’s see what happens if we combine them. We’ll take our code example from the first step: ```js -let food = 'cheese'; +let food = 'cheese' function eat() { - console.log(food + ' is good'); + console.log(food + ' is good') } -eat(); +eat() ``` -Then we’ll wrap *this whole example* into a function, which we’re going to call once: +Then we’ll wrap _this whole example_ into a function, which we’re going to call once: ```js function liveADay() { - let food = 'cheese'; + let food = 'cheese' function eat() { - console.log(food + ' is good'); + console.log(food + ' is good') } - eat(); + eat() } -liveADay(); +liveADay() ``` Read both snippets one more time and make sure that they are equivalent. -This code works! But look closer. Notice the `eat` function is *inside* the `liveADay` function. Is that even allowed? Can we really put a function inside another function? +This code works! But look closer. Notice the `eat` function is _inside_ the `liveADay` function. Is that even allowed? Can we really put a function inside another function? -There are languages in which a code structured this way is *not* valid. For example, this code is not valid in the C language (which doesn’t have closures). This means that in C, our second conclusion isn’t true — we can’t just take some arbitrary piece of code and wrap it in a function. But JavaScript doesn’t suffer from that limitation. +There are languages in which a code structured this way is _not_ valid. For example, this code is not valid in the C language (which doesn’t have closures). This means that in C, our second conclusion isn’t true — we can’t just take some arbitrary piece of code and wrap it in a function. But JavaScript doesn’t suffer from that limitation. Take another good look at this code and notice where `food` is declared and used: ```js function liveADay() { - let food = 'cheese'; // Declare `food` + let food = 'cheese' // Declare `food` function eat() { - console.log(food + ' is good'); // Read `food` + console.log(food + ' is good') // Read `food` } - eat(); + eat() } -liveADay(); +liveADay() ``` Let’s go through this code together — step by step. First, we declare the `liveADay` function at the top level. We immediately call it. It has a `food` local variable. It also contains an `eat` function. Then it calls that `eat` function. Because `eat` is inside of `liveADay`, it “sees” all of its variables. This is why it can read the `food` variable. @@ -192,25 +192,25 @@ Take some time to re-read this, and make sure you can trace this in the code. Here is an example we’ve introduced in the tl;dr section: ```js -let users = ['Alice', 'Dan', 'Jessica']; -let query = 'A'; -let user = users.filter(user => user.startsWith(query)); +let users = ['Alice', 'Dan', 'Jessica'] +let query = 'A' +let user = users.filter((user) => user.startsWith(query)) ``` It may be easier to notice the closure if we rewrite it with a function expression: ```js -let users = ['Alice', 'Dan', 'Jessica']; +let users = ['Alice', 'Dan', 'Jessica'] // 1. The query variable is declared outside -let query = 'A'; -let user = users.filter(function(user) { +let query = 'A' +let user = users.filter(function (user) { // 2. We are in a nested function // 3. And we read the query variable (which is declared outside!) - return user.startsWith(query); -}); + return user.startsWith(query) +}) ``` -Whenever a function accesses a variable that is declared outside of it, we say it is a closure. The term itself is used a bit loosely. Some people will refer to the *nested function itself* as “the closure” in this example. Others might refer to the *technique* of accessing the outside variables as the closure. Practically, it doesn’t matter. +Whenever a function accesses a variable that is declared outside of it, we say it is a closure. The term itself is used a bit loosely. Some people will refer to the _nested function itself_ as “the closure” in this example. Others might refer to the _technique_ of accessing the outside variables as the closure. Practically, it doesn’t matter. ### A Ghost of a Function Call @@ -218,17 +218,17 @@ Closures might seem deceptively simple now. This doesn’t mean they’re withou ```js function liveADay() { - let food = 'cheese'; + let food = 'cheese' function eat() { - console.log(food + ' is good'); + console.log(food + ' is good') } // Call eat after five seconds - setTimeout(eat, 5000); + setTimeout(eat, 5000) } -liveADay(); +liveADay() ``` Here, `food` is a local variable inside the `liveADay()` function call. It’s tempting to think it “disappears” after we exit `liveADay`, and it won’t come back to haunt us. @@ -239,7 +239,7 @@ In that sense, we can think of closures as of “ghosts” or “memories” of ### Why “Closures”? -Finally, you might be wondering why closures are called that way. The reason is mostly historical. A person familiar with the computer science jargon might say that an expression like `user => user.startsWith(query)` has an “open binding”. In other words, it is clear from it what the `user` is (a parameter), but it is not clear what `query` is in isolation. When we say “actually, `query` refers to the variable declared outside”, we are “closing” that open binding. In other words, we get a *closure*. +Finally, you might be wondering why closures are called that way. The reason is mostly historical. A person familiar with the computer science jargon might say that an expression like `user => user.startsWith(query)` has an “open binding”. In other words, it is clear from it what the `user` is (a parameter), but it is not clear what `query` is in isolation. When we say “actually, `query` refers to the variable declared outside”, we are “closing” that open binding. In other words, we get a _closure_. Not all languages implement closures. For example, in some languages like C, it is not allowed to nest functions at all. As a result, a function may only access its own local variables or global variables, but there is never a situation in which it can access a parent function’s local variables. Naturally, that limitation is painful. @@ -247,4 +247,4 @@ There are also languages like Rust which implement closures, but have a separate And with that, I hope you can get a closure on the concept of closures! ->If you prefer a more visual approach to the JavaScript fundamentals, check out [Just JavaScript](https://justjavascript.com/). It is my illustrated course in collaboration with [Maggie Appleton](https://maggieappleton.com/). +> If you prefer a more visual approach to the JavaScript fundamentals, check out [Just JavaScript](https://justjavascript.com/). It is my illustrated course in collaboration with [Maggie Appleton](https://maggieappleton.com/).