Deep JavaScript Foundations, v3

let Doesn't Hoist

Kyle Simpson

Kyle Simpson

You Don't Know JS
Deep JavaScript Foundations, v3

Check out a free preview of the full Deep JavaScript Foundations, v3 course

The "let Doesn't Hoist" Lesson is part of the full, Deep JavaScript Foundations, v3 course featured in this preview video. Here's what you'd learn in this lesson:

Kyle uses a code example to disprove the common saying "let doesn't hoist".


Transcript from the "let Doesn't Hoist" Lesson

>> Speaker 1: One last note on hoisting. Some of you have heard the phrase before, let doesn't hoist with respect to the block scope. Because when you write code like this, the teacher assignment on line 2 will throw an error, a so-called TDZ error, temporal dead zone error, okay? This statement, let doesn't hoist, is meant in good spirit, but it is inaccurate.

And I can prove to you that it's inaccurate, because we've already seen a notion of hoisting and we've already seen a code example. Where if teacher didn't get hoisted and therefore didn't exist in the scope yet, then the program would behave differently. So here is such an example.

In this code, if the teacher on line 5 did not host, then line 4 should print out "Kyle". Because there is no teacher as of line 4 and it should go to the outer scope and find the variable from line 1. But this code still throws a TDZ error, and the reason it still throws a TDZ error is because lets and consts still hoist.

But there is a difference between how they hoist. Number one, lets and consts only hoist to a block, whereas vars hoist to a function. But number two, and this is the more important thing, the difference between var and let const is that var, when it creates its variables and has the plan during the compile time for your scope.

And it says there's gonna be a variable called teacher in this scope, it also says when the scope starts, initialize it to undefined.
>> Speaker 1: When let hoists its variables into its block scope it says create a location called teacher, but don't initialize it. It is in an uninitialized state, uninitialized being different than undefined.

Uninitialized meaning you can't touch it yet. And it doesn't get initialized until in that block you run across the let or the const declaration.
>> Speaker 1: So these absolutely do hoist, which is why we still get a TDZ error, it's just that they don't get initialized.
>> Speaker 1: By the way, I sometimes get asked why the TDZ error?

Why did they make this an error? It is much more nuance than you might realize. I used to believe that the primary reason for the TDZ error in this scenario was that, essentially, TC39 was trying to socially engineer you. To stop assigning to variables earlier in scopes than where you've declared them.

Which again, I generally agree with, but I'm a little annoyed when a language is so heavy handed with its opinions, okay? So I generally would agree with that, and I used to just sort of chaff at the idea that they would have forced me into that decision. But turns out it's much more nuance than that.

This is almost the height of kind of academic nuance, but I'll share it with you so that you have the full perspective on TDZ. The reason TDZ exists is not even for let, TDZ exists because of const. Think about const being attached inside of a block scope. Imagine if const initialized itself to undefined.

And then on line 1 of a block, you did console.log of that variable and you saw it undefined, and then later you saw it with the value 42. Technically, that const would have had two different values at two different times, which academically violates the concept of a const.

So they said we have to prevent you from accessing a variable earlier in the scope than when it's been assigned that one and only one value, so that you can't observe it in that intermediate state. So they invented TDZ for const and then they said, well, if we're gonna do it for const, we might as well do it for let.

>> Speaker 2: [INAUDIBLE]
>> Speaker 1: That's the fact. So let's have a TDZ, because const academically needed a TDZ. My advice? Just exactly as I said before, put all your lets, or if you really insist on colons, put them all at the very top of the block, on the very first line.

And then you'll never be able to observe a TDZ. [LAUGH] Don't string them throughout those scopes, that's a bad idea. Just so that we don't say things, and I have people that sometimes will disagree with me on these topics. And just so that we're entirely clear on this topic of whether or not let doesn't hoist.

Here is actually in the spec where it says that the variables are created when their containing lexical environment is instantiated. When the lexical binding is evaluated, not when the variable's created. So it's right there in the spec. There was a big Twitter war recently where people were telling me that I was wrong on this, and that's what it says in the spec.

Even though we might like to have different mental models for it, it's what it says in the spec.
>> Speaker 3: Why do function expressions not hoist? Could we talk about that in the context of compile time versus run time?
>> Speaker 1: Okay.
>> Speaker 3: Things that happen for the function expression.

>> Speaker 1: Yeah, that's a good question. So I don't know if I could find the slide, but if you recall back, when you assign a function expression to a variable, the variables decoration itself hoisted, but the assignment is a run time thing. Imagine mentally the idea of JavaScript reordering your executable code.

We would quickly descend into chaos, right? So executable code can't conceptually be reordered, only declarative code. So we can say that there is a thing called X, or a thing called teacher, and even there is a thing called X that's gonna reference this function scope. We can do that declaratively.

But if it's something that is literally observable as an execution operation in a program, that can't hoist, that can't happen at compile time. It has to happen at run time. And it has to happen where in the program it would normally happen. Otherwise you would never be able to predict your JavaScript.

>> Speaker 3: So at run time if you're assigning a function expression to a variable targeting, then is a new scope created during that run time process?
>> Speaker 1: Well, let's be clear. You're asking a very important question, but there's this two layered on top. Number one, compile time still handled the function.

It didn't defer handling, it's not like it said I don't know that there's a function there. It's still a function, it still has to have a plan, a map for its colored marbles and all of that other stuff. What didn't happen is it didn't get, at compile time, associated with some identifier in the scope, but it still got compiled, okay?

So that's number one. Number two, all functions have a plan for their scope, until, and every time they get executed. So whether it was a declaration or an expression, when it gets executed is when that whole mapper plan becomes a real thing in memory, and every time it gets executed, that is the case.

So that's no different between declarations or expressions.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now