This course has been updated! We now recommend you take the Deep JavaScript Foundations, v3 course.
Transcript from the "Closure Examples" Lesson
[00:00:00]
>> [MUSIC]
[00:00:04]
>> Kyle Simpson: You can also return functions from function. So passing them as parameters is one way, returning them. Here I take the inner function. In this case an inner anonymous function from lines four through six. I return it out. So on line ten, the first set of parentheses gets the function.
[00:00:20] The inner function back. And in the microsecond between the first set of parentheses, and the second set of parentheses, that function object has been transported outside of his lexical scope, and yet he's still able to access the lexical scope and that is closure.
>> Kyle Simpson: Questions so far?
>> Kyle Simpson: This should start to look a little more familiar with some of these examples.
[00:00:47]
>> Kyle Simpson: When you pass a function into a set timeout. Any kind of callback. And that function is able to remember the variable. The way it does that, if you think about it, inside of the engine somewhere there's a set timeout utility. And he gets a little callback called CB and he'll execute them at some point.
[00:01:04] He's executing your function well outside of his lexical scope. But your function still remembers the lexical scope, and that's because of closure.
>> Speaker 1: In this particular example, what if bar were to change, the value bar.
>> Kyle Simpson: Yeah.
>> Speaker 1: Were to change within given 100,000.
>> Kyle Simpson: That's exactly what I meant by saying it's not like a snapshot.
[00:01:25] It's an actual live link back to the actual scope. So if you changed it between the time you set it up, and the time the timer fired, it would access the current value at that moment.
>> Kyle Simpson: Another example, if you're familiar with any kind of frameworks that do click handlers.
[00:01:45] You set up your click handlers and your click handlers are able to remember something about their environment. That's entirely because of closure. Closure is a necessary mechanism for a language with first class functions as values to be useful. If functions could be passed around with their values but they couldn't remember anything about their lexical scope, nobody would pass functions around.
[00:02:08] The only reason why we find this useful is because they implicitly have this capability called closure.
>> Kyle Simpson: This again references, it doesn't matter how many functions have a closure over the scope, they all have a closure over the same scope. You can think about scope, by the way this is another way to kind of process it in your brain.
[00:02:33] You're probably familiar with the idea of garbage collection in JavaScript. Maybe that might be a little bit confusing to you, but let's just say that in JavaScript if you keep a reference to an object around, that object doesn't get garbage collected. So if you had ten different references to it and nine of them went away, but one of them was still there, it still wasn't garbage collected.
[00:02:51] Soon as the tenth reference goes away, now it can garbage collect it. The same, similar kind of mechanism is at work with your scope. When you execute a function, it creates a scope object. If there's anybody that gets a reference to that scope object via closure, that scope doesn't get garbage collected when the function ends.
[00:03:09] It keeps the scope around and it can continue to keep modifying it and accessing it. As long as there's still one function, at least one function with a closure over the scope, that scope doesn't go away. So here we've got two different functions. The one on four through six and the one on seven through nine.
[00:03:26] Both functions have a closure over the same scope. Which means when the first one runs he updates bar, when the second one runs he updates the exact same bar.
>> Kyle Simpson: It's all levels, it's not restricted. So whatever the entire nested deep level is, here we have a bar referencing one level up.
[00:03:47] This bar is now referencing two levels up. The exact same bar in this baz is referencing a different. So it maintains the entire scope however nested and deep it is for as long as is necessary. That is what we call closure. Questions so far about closure? All right, now this is the canonical example for illustrating how closure works, and it references this idea with loops and with functions inside of them.
[00:04:19] The spirit of this exercise, is that I would be able to loop through, and have at the one second mark, have it print out i1, at the two second mark have it print out i2, at the three second mark have it print out i3, i4, and i5. I want each one of you to click the code me icon down in the bottom left, and I want you to tell me what it prints out in your console.
[00:05:01]
>> Kyle Simpson: What are you getting it to say?
>> Speaker 2: Six is to six.
>> Kyle Simpson: Prints out six. Five different times, right? So the reason for that is what's wrong with our understanding of closure, and that's why we show this example, to kind of explain when closure's not behaving the way we want, it's because we have an incomplete understanding of what the closure would really mean.
[00:05:22] We have here this implicit idea, normally I would kinda belabor this as a big exercise, but just to keep us on track, I'll just go ahead and shortcut to it, that what we have here is we need to ask ourselves what's missing from this example that's preventing it from working.
[00:05:39] So the way we could ask the question is if I'm getting i66666, well, we can explain why that happens pretty clearly. It's that the i at the end of loop ends up as 6. And so when these functions run 1,000 and 2,000 and 3,000 times, obviously i is already 6 by the time those functions are running.
[00:06:01] But what's really wrong is, why don't we have five different i values? In other words, why don't we have five different i's? Because for some reason the way our developer brains work, when we did this loop, we seem to think that these functions, we're getting a whole new i for each iteration of the loop.
[00:06:25] But they're not, are they? There are five different functions that are closing over the exact same global scope. It's no different than if I wrote the set timeout one after the other five times on top of each other like I did a couple slides ago. Something about the loop confuses us into thinking that there's something more magical going on.
[00:06:43] This is five separate anonymous functions that close over the exact same scope, that is the global scope that only has one i in it. So,we can ask the question, what's missing? What could I do to this piece of code that would allow it to have a different i for each iteration?
[00:07:01] To have a whole different scope for each iteration. And that solution is typically the iffy. Remember how functions create scope. So if we were to put in an iffy inside of the loop, each loop would get its own i. And the functions inside of them, if you were to try this code, if you click on the code me and you try it, it will now run correctly.
[00:07:23] It'll print 1, 2, 3, 4, 5, because we have a whole different scope created for each iteration. So each one of those functions is closing over an iteration scope rather than just over the global scope.
>> Kyle Simpson: [COUGH] Yeah?
>> Speaker 3: If you use let-
>> Kyle Simpson: We're getting there, wait just a minute.
[00:07:45] This is the canonical solution to it is that you put an if E inside of the loop, because what was really missing was that we wanted a scope for each iteration. Everybody with me on that?