September 4th, 2023 × #javascript#closures#scope
JavaScript Closures & Scope Explained
Scott and Wes explain JavaScript closures, scoping, and give examples of how closures can be useful.
- Introduction to local scope
- Global scope in JS
- Local vs global scope
- let/const vs var for block scoping
- Block scope syntax
- Lexical vs dynamic scoping
- Function scope creates closure
- Inner functions maintain outer scopes
- Closures allow inner functions to access outer scopes
- Dynamic scoping is uncommon in modern languages
- Closure for private variables
- Closure example with Svelte
- Modules use closure
- Closure for caching
- Closure for reusable logger
- Closures used more in functional programming
Transcript
Announcer
Monday. Mon Monday. Hey. Open wide dev fans. Get ready to stuff your face with JavaScript, CSS, node modules, barbecue tips, get workflow, breakdancing, soft skill, web development, the hastiest, the craziest, the tastiest TS Webb development treats coming in hot. Here is Wes, Barracuda,
Wes Bos
Boss, and Scott
Scott Tolinski
ghee. Welcome to Syndax.
Scott Tolinski
On this Monday, hasty treat, we're gonna be talking about closures. Now, closures are one of those things in JavaScript that have kind of a weird name. If you're coming to JavaScript script or programming. You're new to this stuff. You see the word closure.
Scott Tolinski
It sounds interesting. It sounds maybe perhaps confusing. So we're gonna be talking all about what closures are, how to reason about them and and what what exactly like, why are they something that people even talk about, or why are they interesting? My name is Scott Tolinski. I'm a developer from Denver, and with me as always is Wes Moss. What's up, Wes? Hey.
Wes Bos
Not too, too much. We have had this on the list for probably a couple of months now Yeah. Explaining closures. And, like, I've I've done the, like, explained closures.
Wes Bos
I did it in my beginner JavaScript course.
Wes Bos
Check it out if you like this show. You can buy it. It's really good.
Wes Bos
I've done it a couple times when when teaching and whatnot, and it's always one of those, like, tricky things to to sort of do because it's a, it's scarier. It sounds scarier than it actually is. And b, You gotta have, like, real world examples of, like, when you might use that. Otherwise, it's just a, like, bizarre concept that you might not totally understand.
Scott Tolinski
Yeah. And it doesn't help that it goes along with, in general, scope in scoping, which is, like, always one of those things that I I feel like Causes people's eyes to glaze over a bit when they're learning about scoping or, it's it's not a ton of fun. So we're gonna try to make it fun.
Scott Tolinski
Perhaps we will bring in a sandwich metaphor. I don't think so. But, you know, who knows? With Wes, you never know if the sandwich metaphor is coming. So let's talk about scope overall. 1st quickly, so the different types of, like, macro to microscope that we have. So for instance, we have a global scope, and that is everything that you can access globally.
Scott Tolinski
You define a variable on the global scope. You can reference that variable anywhere in your application.
Scott Tolinski
And, typically, things aren't meant to be put too much in the global scope. If you pollute the global scope too much, You're gonna have weird things where you're using a variable, and all of a sudden, it's coming up with a value because it's been defined somewhere else. So We have used ways in JavaScript to limit the scope in which we're currently working in instead of having this giant global scope full of everything.
Introduction to local scope
Scott Tolinski
In the browser, you have like, the window is a global scope. Right? You can access things For the window, for the entirety of the the browser scope anywhere in your application without having to even import or reference anything. It's just available to you. That's on the global scope.
Global scope in JS
Wes Bos
In other platforms, It's it's actually really hard to make global variables these days. It used to be you had a script tag, and you load in the script tag. Any variable that you declared inside of that JavaScript file, if it's not inside of a function or it's not inside of an if statement or anything like that, Then you've just created a global variable. And that's why you can type a variable in a script tag and then go into your dev tools and type it in in the console and see what that actual value is.
Wes Bos
So it's pretty tricky to actually do that anymore because We are mostly all using modules. Module we'll talk about that in just a second, but modules have their own scope. In Node. Js land, There is in pretty much every other platform, there is a variable called global this. It's even available in the browser.
Wes Bos
So if you want to stick something on Global Scope for whatever reason, you can use the variable global this, like camel case, and that will give you access in Node. Js. That will give you like, if you have 1 file and you want to be able to access it in another file without importing or exporting or passing or anything like that, you can stick it on Global This and you'll have it available to you. But you should not do that.
Local vs global scope
Wes Bos
So let's go with scope. Then we also have what's called local scope. So Local scope is variables that are available inside of a block.
Wes Bos
So a block in JavaScript is created when you have little code fences. I like to think of the curly brackets. Fences. Oh. Code fences. Yeah. Curly brackets in JavaScript keep your variables in. That's what they're there for. Right? And every time you have a set of curly brackets, except for an object, like an object is not a scope. But if you have an if statement or if you have a function or you have a case switch or any of that, those curly brackets or or a for statement are creating their own scope, meaning variables created inside of those curly brackets. Let and cons variables are not available outside of the gates of your curly brackets. Right? And that's really nice because you can go willy nilly making variables inside of a loop or anything like that. And you know that there is no code before or after it that you are going to be accidentally overwriting or they're not going to leak out and just have some random thing. Var variables are function scoped, and let and const are block scope, meaning that if statements, for loops, functions, literally anything with curly brackets.
let/const vs var for block scoping
Scott Tolinski
Yeah. And this is one of the reasons why people often say to use Let or const over var nowadays because it can be a little mysterious if you're assigning a variable inside of a block scope, and then you're accessing it outside of a block. And you're like, why did this? You know, why is this using the the value that it is? And it's more predictable if you know that, like, a block is containing the variable itself. So, I posted a little little syntax for just a a JavaScript block. And and it's funny. I almost never use straight block syntax.
Scott Tolinski
Do you ever use that where you're just using the curly brackets without anything else, like an issue? Or
Wes Bos
The only time I use that is in dev tools Because I want to re rerun the same code over and over again.
Wes Bos
And in dev tools, when you create a variable, you're creating a global variable. So if you wanna rerun that code, it says, ah, you already created that variable. Chrome DevTools will allow you to override it because it realizes you're doing, like, a iterative process, but not in Firefox. So if you just wrap a set of curly brackets around your code, you've just created a scope. Right. And you can't access the variables inside of that because you've You've created a closure, really. So you you can't, get it, but it is a nice way to
Scott Tolinski
to make its own scope. Yeah. And if you don't know what I'm talking about, you can just do
Wes Bos
open bracket, close curly bracket, and write some code in there, and it creates a block. Exactly. So we have global scope, we have local scope, and then you have this thing called enclosing scope, which is when you have multiple scopes nested inside of each other. So if you have a parent function called say hello.
Block scope syntax
Wes Bos
And then inside of that function, let's say you have a variable called name equals west, const name equals west.
Wes Bos
If you have an if statement inside of that function, you've now created 2 scopes. Right? You have your function scope, and then you have your if statement scope.
Wes Bos
And the way that JavaScript works is if you are trying to reference a variable inside of a scope, JavaScript will first check the local gates of curly brackets and say, hey. I'm looking for a name variable. Is there any variable side of this if statement or inside of this for loop or inside of this scope named name? If so, that's the one I want. If not, it says, alright, folks.
Wes Bos
I didn't find what I wanted. I'm gonna go one level up. So that goes one level up to the function scope and says, hey, any variables here named name? And if that is true, then it finds it.
Wes Bos
That's the reference it uses, but it will keep going up scope levels until it finds what it's looking for. And eventually you're going to hit global scope. And if it's not there, then you're kind of pooched because that variable exists nowhere. So the scope lookup
Scott Tolinski
Goes kinda up the tree, every set of curly brackets. So if we wanted to to bring in the sandwich metaphor here, you could think, like, 1 scope could be the bread. You'd say, is there any cheese in here, and it's checking between the 2 pieces of bread first. Yeah. Well, there's no cheese in here.
Scott Tolinski
And then maybe the next scope out would be, within your household. Is there any cheese within your household? And then maybe the next scope out would be, is there any cheese in my 20 minute radius?
Wes Bos
20 minute radius would be. Like, it just keeps going up a level, expanding its scope per se Yep.
Wes Bos
To find exactly what it's looking for. So functions make Enclosing scope.
Wes Bos
Probably the most popular scope that you're not used to is module scope. So every single time you have a import or an export or you load something in via type of module, that JavaScript file itself is a module, meaning that variables created inside of that module will not leak outside.
Wes Bos
They're not available to other files unless you explicitly import and export them. And that is a super handy feature of modules because you're not accidentally creating all this Global scope. You don't have to. In the past, we used to have create what's called an IFFI, an immediately invoked function expression, which is basically just a function that immediately you put parentheses on the end and it calls itself.
Wes Bos
And the benefit of that is that it It's creating what's called the closure. Right? We'll we'll talk about in a second. We should talk real quickly is that JavaScript is lexically scoped. You may this is the an interview question. Right? Some programming languages are dynamically scoped, and most programming languages are lexically scoped, meaning that variables are determined by where the code is defined, not where it's run, meaning that we go through the whole example of just going up the scope chain.
Lexical vs dynamic scoping
Wes Bos
It's literally looking where into its parent function where the code was written, not where the variable was defined. So if you have a function that internally accesses something called name, and Right above that function, you you say, like, const my name equals wes, and then you run the function called display name.
Wes Bos
Those 2 things have nothing to do with each other unless you explicitly pass the value into it and and introduce that value into the scope.
Wes Bos
So lexically scoped means the scope is determined by where the variables are written in your code, not where it's run, and that's most I would say most programming language. I wonder what languages are dynamically scoped. Oh, I'll I'll look that up while you take the next set section. So closures.
Wes Bos
Now that we understand scope, we have this idea of a closure. So when you create a function, that function has scope.
Wes Bos
And when you call that function, variables inside of that function are created.
Function scope creates closure
Wes Bos
When the function is done running, that function is closed and the variables disappear.
Wes Bos
They're garbage collected.
Wes Bos
So you have a function called say hello.
Wes Bos
And inside of that function, you say const sentence equals hello, Wes, and then you return hello, s, or you do a little bit of math inside of there. You are dog years old. You create a variable of age, And then you inject it into the sentence. So you just you just made a variable quickly, threw it into a string, and now you're done with that. So the variables that were created inside of that function to do the work are gone, and they are garbage collected, meaning that the browser put them into memory real quick, did the work and then it closes. Alright. Well, like, literally nothing else is still pointing towards this variable.
Wes Bos
So I'm just going to throw it out because there's no sense in keeping it around because eventually we're going to run out of memory on the user's computer. Right? That's what garbage collection is.
Inner functions maintain outer scopes
Wes Bos
So that's a basic function being being run. It's closed. But If you put a function inside of another function, let's call that an inner function, you can return it to call it later. Right? So you can have a function that runs immediately, and then inside of that function, you return another function That is is not run yet, but it's just you've created a you dynamically created a function says, okay. Here it is. You can go ahead and call this later.
Wes Bos
So that inner function can still go up the scope chain like we were talking about earlier. Right? So If you talk about that that inner function being able to access a variable from the outer function, What happens to that variable? I just said the very once the function is done running, the variables are garbage collected. But what happens if the inner function is still referencing a variable from the outer function, and that is what is referred to as a closure, meaning that You've created this ominous space where the variable is inaccessible to you because the function is done, But that internal inner function can still access the outer variable.
Wes Bos
And you've sort of created this little neat little nesting spot for variables that you can still access and reference and update and return the value from there. So that is what is referred to as a closure because the outer function has been closed over, but its own scope lives on in memory. It's not going to be garbage collected because something is still referencing those variables internally.
Closures allow inner functions to access outer scopes
Wes Bos
So the browser knows, okay.
Wes Bos
I can't throw those out just yet because something is still pointing towards them. So dynamic scoping is
Scott Tolinski
not super common as a main feature in modern languages, but it does seem like several languages Offer the ability to do dynamic or lexical scoping.
Scott Tolinski
It says early versions of lisp, Emax lisp.
Scott Tolinski
Some versions of Pearl have a keyword for dynamically scoped with the local keyword.
Scott Tolinski
This also says, bash and other shell scripts. But when I dug into it, I did find a Stack Overflow post where somebody says there's no there is no dynamic nor static scope in bash. So I it it's I you know, it's it's not, like, super crystal clear. It's Mhmm. Noble four hex. So almost nothing. TCL. Yeah. Almost nothing. Languages that you have not heard of or almost always, like, older kind of programming concepts or languages.
Dynamic scoping is uncommon in modern languages
Wes Bos
So let's talk about some examples of where a closure might be handy. All right. So one example is and I'll link up the actual code for this from my website, as I have all my beginner JavaScript course notes for free on my website, is private variables. So you have a function called create game, It takes in a game name, so hockey, soccer, basketball. Right? And inside of the create game, you create a score variable that's called let score equals 0, and then you return a function called win.
Wes Bos
And every single time You call that win function. It will increment the score variable over and over again. So what you can do is you say like const hockey game equals create game hockey. So you're passing in a variable and you're creating a score variable That is set to 0. And every single time that you run that return function, it's going to reach into the closure for 2 things, the score, which you can then update. It's a private variable.
Wes Bos
And the second, which is the name of the game, and it just says your hockey score is 1. Your hockey score is 2. Then you can go ahead and create another one. Create game soccer.
Closure for private variables
Wes Bos
You can say soccer win, right? And it will say your soccer score is 1. Your soccer score is 2. So that is a nice way to sort of like Create.
Wes Bos
They're often called factory functions, where you are generating a function that is sort of preset with a bunch of data. And even Scott made a generator function in the what was it, the sorry. Factory function. Do you wanna explain the one that you didn't in our our Svelte site? Yeah. Well so
Closure example with Svelte
Scott Tolinski
the the one that I did for the Svelte site is for a Svelte action. The way that you can do, enhanced forms inside of SvelteKit is by having a funk or having a function directly in the use enhance action. The use enhance action, accepts some things.
Scott Tolinski
However, I I found that you couldn't necessarily pass in your own variables to this thing. You couldn't have your own, essentially your own function each time this was going to be run, so I created a factory function that created the function for me with the properties and the variables and the scope that I needed each time the enhance was run.
Scott Tolinski
So anytime my enhancing to be run, it wouldn't just call a function. It would create the function
Wes Bos
and then run that function. That that was a really good example because you. You have to pass the initial data when the thing is loaded, but What is returned does not. It doesn't run the function immediately. It returns you a new function that is sort of preloaded or bound with the new data.
Wes Bos
Another example of closures is I think this is probably the most common one is modules. Modules even though modules aren't a function per se.
Modules use closure
Wes Bos
They still have their own scope, and they still are sort of a closure in themselves.
Wes Bos
So if you have like a serverless function or if you have really like a route handler in Node. Js or anything like that, if you create a variable that is outside of your route handler and whether that is a counter for how many times a function has run Or my case, I often will create a cache for saving data in my OG images I talked about earlier.
Wes Bos
The handler will reach outside of the function scope into the module scope, which is is maintaining state from the last time that it ran, and it says, alright. Well, if I already generated this image, let me reach outside of my scope and see, does this exist in the module scope, right? So you're essentially creating a little closure there where you put the cache outside of the handler and you can reuse it from function call to function call.
Closure for caching
Wes Bos
I have a couple more examples here. Asynchronous work is not as popular. It was very popular in the callback world where a function could run, but you have to wait a number second. It's it's not as popular anymore now that we have
Scott Tolinski
Promises.
Wes Bos
Promises in a single way, but it kind of still works the same way. I'll link up the tricky bits closures from my website. You can read through it. Just get a couple more examples in there to totally understand it. But hopefully that gives you a good example of what closures are. Essentially, they are just scope that can be passed around and maintained past The fact after they've died.
Scott Tolinski
Yeah. Which is it's so funny because, again, it does seem like such a complex thing.
Scott Tolinski
And even here, you might be listening to this and thinking, like, I don't have any use for this, and that's totally fine.
Scott Tolinski
There are times In your development career, when you will have this moment, and you're like, it all makes sense now. I understand, And it's not that big of a deal.
Scott Tolinski
I get closures. You know? But it it for some reason, I don't know what it is. It's one of those topics that just sparks a lot of confusion for some reason, and it I'm not immune to that as well. I until I, like, had to write these types of things, I I had no idea
Wes Bos
what the use case is for me to even use this type of thing. So One more example I have is if you want to create like a logger function instead of, like, let's say if I wanted to console log the syntax show name, the syntax show number, the date, and any guests that we have.
Closure for reusable logger
Wes Bos
And any time we do anything, we load a page, we update something, we delete it.
Wes Bos
You can use a factory function and you say create logger, pass it the whole show, and then from that you can return, like, a log function where you can just say log Loading page. And then because that function has been passed the show earlier, it has access to the entire show In its closure scope, and it can console log the show name and the show number and the guests and all of that data. And you don't have to, like, Carry around or reconstruct the entire logger every time. You can simply just say log page loaded, and then the console log will look like law page loaded for episode 4 94 titled Blah Blah Blah with guests, Scott and Wes.
Wes Bos
So it's sometimes nice to just tuck away complexity into its own function and then return a new function
Scott Tolinski
You can call at any time, and it still has access to that data. So another thing to note that I don't know if you you specifically mention because people might be listening to this and be wondering, like, Yeah. But why don't you just use a class for some of these things? Right? And that's really, really one of the the pieces is that the closures are used less in object oriented programming, and they're more associated with, like, functional programming topics.
Scott Tolinski
So if you're you're out there and you're using classes and stuff, you you might not run into this as much. Yeah. Because a class allows you just to stick that stuff
Closures used more in functional programming
Wes Bos
on the class itself.
Wes Bos
Right? And you can just store it in there. And with the function, you can't just stick the stuff anywhere. So you have to sort of create this idea of closure. So, yeah, you're right.
Wes Bos
That is a very good use case. You still will run into closure use cases on class methods, but not nearly as much if you're doing As if you were doing functional work. Word. Alright.
Wes Bos
That's it for today. Hopefully, you enjoyed that. We will catch you later.
Wes Bos
Peace.
Scott Tolinski
Head on over to syntax.fm for a full archive of all of our shows, And don't forget to subscribe in your podcast player or drop a review if you like this show.