Transcript from the "Hoisting" Lesson
[00:00:40] Typically, I'll get different kinds of answers. Some people will say, well I'm gonna get an error cuz A doesn't exist. Other people are gonna say well A exist but it's gonna be undefined. And still a third class of folks would say A is gonna be the value 2, cuz they're sort of thinking it runs like all the way through it and then hoist the value up or something.
[00:00:58] Only one of those can be the correct answer. Let's be clear. So which is it? Which is the correct answer? Given what we now know about how the compiler works, we can say definitely there will be an A because it would have found the var A on line 3 at compile time.
[00:01:14] Registered it to the scope and then when we tried to access it on line one there is a variable but at that moment it has no value so it has the undefined value. That's a very simple explanation but it requires you to think about the two paths, and sometimes people don't like to talk about the two paths thing.
[00:01:55] Looked at this way, you could see very clearly how we could come to the conclusion of what happens with line 3. Surely there is a var a because we saw it on line 1, but surely it has no value because we haven't assigned it on line 5. So, it's a shorthand to describe what we already know.
[00:02:11] Which is that variables belong to their enclosing functions.
[00:02:56] That doesn't even make any sense. It would have to parse to figure out what to move anyway. But even if that was not what was happening, I mean it isn't what was happening, but even if it was what's happening, it's a nice shorthand for us to describe what's really going on with the two-pass system.
>> Kyle Simpson: And it goes not only for variable declarations, but also for function declarations. Heads up if you've been wondering why should I use a function declaration over a function declaration. Here you're about to get an answer to that question. Variable declarations and function declarations are hoisted but assignments, logical, actually executable code, that can't get reassigned.
[00:03:34] I mean that can't get relocated because the relocation could break the whole logic of our code. So, we cannot reorder executable code. We can only reorder declarations. So, this code, properly thought of in the hoisting metaphor, would look like that. We hoisted the function b, then we hoisted the vars a, c, and d.
[00:03:55] But you'll notice that on line 11 we still have the execution of the assignment of that function expression to a variable d. So in other words, the function expression didn't hoist. Cuz it's executable code, it can't hoist. That would create havoc. So, we can use function b on line 7, but what would happen on line 8 when we try to use function d?
>> Speaker 2: Undefined?
>> Kyle Simpson: Well, d would be undefined on line a. So if we try to execute an undefined value, we're getting get a type error, you're not allow to execute undefined. So when you ask the question, why do you prefer a function declaration over a function expression? One of my answers to that question is, I prefer to have function hoisting so that I can declare a function at the bottom and use it above, and know that's gonna be hoisted.
[00:04:48] I prefer that style of code.
>> Kyle Simpson: How many of you have ever heard of recursion before? Okay, how many you have heard of mutual recursion? Mutual recursion is when two or more functions reference each other in a recursive call stack. Here I've got three function, a calls b, b calls c, c calls a.
[00:05:11] Thinking about mutual recursion, which is a particular technique that is good at solving some particular types of computing science problems. Mutual recursion is quite useful. But if we did not have a notion of function hoisting, it would be impossible to express a mutually recursive program. Because one of these functions would always be declared too late.
>> Kyle Simpson: We can move b before a and c before b, but then a needs to move before. We're never gonna be able to express these functions in the correct order because they have a circular dependency on each other. Function hoisting allows us to do that. How many of you have written C programming code before, anybody?
[00:05:52] Do you remember writing header files? What was the most common thing you put into your header file? The function signature, right? Java stopped to wonder why you needed to do that. The reason you had to do that is so that the compiler, the GNU Compiler or whatever yours was using, so that it knew what the functions were.
>> Speaker 3: Can you go back a little bit into the hoisting again cuz I've been taught that always avoid function declaration in favor of name.
>> Kyle Simpson: There's a lot of misinformation out there.
>> Speaker 3: Right, so I'm a little, okay.
>> Kyle Simpson: Let me try to illustrate it in this way.
>> Kyle Simpson: Go over to my editor.
>> Kyle Simpson: If I said, that program, would we all agree that it's gonna work? Yes, cuz we're gonna find the var, or it's gonna hoist, however you want to describe it.
[00:07:04] But would we agree that that's not a particularly good style of code? It's kinda designed to sorta be confusing to people, right? So because you can do that, people typically say hoisting is bad, don't do hoisting. Okay? What about this program?
>> Kyle Simpson: Is that program gonna work?
>> Kyle Simpson: Do we feel exactly the same about that style as if we're saying that function needs to be written earlier in the file before we use it?
[00:08:04] What if I just pull all of my executable code at the top? It'd be a lot easier to find, right? And because functions hoist I don't need to worry about that anymore. So I just go ahead and put a little comment there, and draw myself a little visual line.
[00:08:19] And I know that all of my executable code is gonna be above that line. And that's usually the stuff I care about. All the functions will be listed below. Doesn't matter what order they're listed in, because they're all gonna get hoisted, and they're all usable. So I made the decision from a code style perspective, function hoisting is a really good useful thing.
[00:08:39] Variable hoisting not so much. Should we throw out all of hoisting, should we say all of hoisting is bad? I think this example of usage of hoisting is actually pretty good. Now, different people have different styles. My recommendation is to consider this approach. Because I have actually implemented this now for the last several years, and I find my code to be a lot more readable now that I do it this way.
[00:09:03] And by the way, I do it with every single scope even if it's a function within a function, I put the function declaration at the bottom of the outer function and I put all my executable code at the top of the function, cuz the exact same principle applies, it's easier to find the executable code.
[00:09:17] So, if foo had some function bar inside of it that did something, [COUGH] I'd have the function bar at the bottom. And then I'd have a line that demarcated where all the function stuff was. I'd have all my executable code here, including a return statement. So it's very easy and quick to figure out in a function what's it gonna do, and not have to scroll through potentially thousands of lines of inner functions to find where the executable code is.
[00:10:07] The function hoisting that's occurring is what's allowing us to do this. So it's allowing us to call a(1) up on that line, even though a hasn't actually been declared yet, because the compiler already declared that for us. The compiler already handled that part for us. Some of you may be wondering in your head, what does this return?
[00:10:31] Sometimes we'll take an example to try this out. I'll just go ahead and tell you this returns the value 39. It specifically does not return the value 42, cuz that's what everybody guesses, okay?
>> Kyle Simpson: Last point to make on hoisting, back to our friend, the let keyword. You remember that I said that let key words cant be duplicated.
[00:10:56] One of the other behaviors of the let key word, it's often said that the let does not hoist. You'll notice here I'm using let baz on line 4 but I'm referencing baz on line 3. If we'd done that with a var that would have worked just fine. When we do it with a let, we're gonna get an error.
[00:11:14] Specifically, and I did not make this up, specifically, that error is called a TDZ, a temporal dead zone error. Don't know where they came up with that name, it's pretty crazy to me. But the temporal dead zone error is using a variable in the period of time from the beginning of that lexical environment to the point at which that variable has been initialized.
[00:11:37] Now, we think of line 4 as the initialization, but I'll just tell you the initialization has nothing to do with the equal sign. If line 4 said let baz semicolon, that would still be the initialization of baz on line 4. So it's not about the assignment part. There is a nuance here that you only get if you dig into the spec like nerds like me do.
[00:11:58] There's a nuance here that when you use a var keyword, two things happen. Only one of which is actually hoisting. What we really mean by hoisting. The first thing that happens when a var keyword is used, is that variable is added at compile time to the enclosing lexical scope.
[00:12:17] We already know that part. The second thing that happens, which we didn't talk about, is that at runtime, when that scope starts, when that scope is entered, any variables that were added to the lexical environment get initialized to their undefined value. So they are then available to be used throughout the entire scope.
[00:12:38] Let keywords only do one of those two things. Let keywords add their declaration to there enclosing lexical environment scope, but they do not initialize them at the beginning of the scope. They only get initialized at that undefined value at the presence of the let keyword. So from the beginning of our environment to the moment that the let keyword for that declaration shows up, it is in an uninitialized state and therefore cannot be used.
>> Speaker 4: What is the reasoning for that?
>> Kyle Simpson: Const is the same. Although you can't reassign a const, so you can't actually separate them.
>> Speaker 4: What's the reasoning for that, why doesn't it get hoisted like a bar?
>> Kyle Simpson: So, let me separate out your two questions. What's the reasoning for this behavior?
[00:13:29] Ostensibly, the reason for the behavior is exactly what I said before, it's bad code style to say a equals 2 var a. It's bad code style here to say console.log(baz); and then assign baz on a different line, right? There no really a functional problem, it's just a bad coding style that can be more confusing.
[00:13:47] So, ostensively they put in an error to prevent you from doing that. There's more nuanced reasoning to it than that. That's the most easiest way to explain.
>> Speaker 4: Thinking that if you used a variable before assigning it, before declaring it, that's potentially a mistake that you've made.
[00:14:03] So it's trying to help you by saying this is.
>> Kyle Simpson: That's essentially what they're saying. It's probably a mistake, it's probably a stylistic problem. So you shouldn't do it so let's go ahead and make it an error. I would agree that if you use a variable before you've declared it, it's probably not a good idea.
[00:14:22] But as I just illustrated using functions before you've declared them makes a whole lot of sense. The second point, I'm gonna get to your question but the second point you said that it doesn't hoist. A lot of people say let does not hoist. While it's convenient to say it that way, that's not technically accurate, the hoisting part is adding your variable to the enclosing lexical n scope and both of them do that.
[00:14:50] Hoisting does not actually describe the initialization point, that is a separate thing and that's what different between var and let. So technically let still hoist. They just don't initialize it at the beginning of the environment scope like vars do. And it's that lack of initialization that they kept out so that you'd get an error if you used it temporarily or too early.
>> Speaker 4: I know it's a
>> Kyle Simpson: There's no difference here with strict.
>> Speaker 4: Same function declaration hoists and.
>> Kyle Simpson: Yep, there's no difference with lexical scope and.
>> Speaker 4: No, I mean like in the strict mode, if you reference var before declaring it
>> Kyle Simpson: It's not a strict mode error.
>> Speaker 4: Your example where you say a equal 1, and then you say var A?
>> Kyle Simpson: Yes. What I'm saying is in strict mode, it's not an error. It behaves exactly the same between strict mode or nonstrict mode.
>> Kyle Simpson: The nice convenient shortcut, cuz hoisting let's be honest, hoisting is a shortcut definition, it's an easier way of describing, the nice easy way of describing it, which is what most people say is let's don't hoist.
[00:16:07] Okay, so if you don't wanna be super nuanced about the actual spec language, you can just say lets don't hoist.
>> Speaker 5: It does, it just doesn't get initialized.
>> Kyle Simpson: Right, that's the actual answer. It does hoist, it just get initialized.