JAKE: So-- SURMA: I just want to have
a count of how many videos we started with "so." [LAUGHING] Hashtag natural. [MUSIC PLAYING] JAKE: So last time we spoke
about code on HTTP203. We looked at tree shaking,
pipeline operator, the bind operator, like really
future-facing stuff. But I thought this
time we'll go and look at something that has been
there forever or for a long time and how a feature has
developed over time-- this. SURMA: That's a for loop. JAKE: Well done. You're still good at quizzes. Excellent. SURMA: One point for me. JAKE: So I wanted to talk
about how far loops actually work under the hood, right. SURMA: You say
"actually" as if there was a big twist to the story. JAKE: Well, here's how it goes. So this bit runs, right? SURMA: Mm-hmm. A for loop has
three instructions, the initialization, the
condition, and the incrementer, or whatever? JAKE: Sure, yeah. SURMA: Iteration. JAKE: They're just expressions. So this one runs. And this one runs. And then the body runs. And then this one runs. And then this one runs. And then the body runs. And then this one runs. And this one runs. And the body runs. And this one runs. And it only stops if this bit
evaluates to something false-y. And that is how for loops work. [MUSIC PLAYING] [DISK SCRATCH] But it has gotten
more complicated in the past few years. SURMA: I haven't
learned anything today. JAKE: I mean, that
was all correct. It is just that. For loops were
really, really simple. SURMA: I mean, they have been
in the oldest programming language in exactly this format
with these three statements that get evaluated in that
specific order over and over. JAKE: Let's have a look at this. What is going to be logged
in this piece of code? SURMA: What we have in here,
which is kind of hidden, is a closure, right? So-- JAKE: Yes, yes. SURMA: We're passing a closure. The set time was like,
Execute this closure in-- JAKE: Some amount of time. SURMA: Apparently, it's
zero milliseconds, I think. JAKE: It would default to zero. The browser will
add a padding to it. SURMA: Four. JAKE: Four, 15-- the browser
is allowed to do whatever. But the key is
that this is going to happen asynchronously
after the for loop has been completed. SURMA: It would close over the
i variable, which in this case is a primitive value. And therefore, the
value will be captured. So this would mean that we
will see free logs, zero, one, and two. JAKE: Not quite. There is only one
i in the story. SURMA: Interesting. JAKE: And that's
going to be scoped to the parent function
or the global scope if there's no parent function. SURMA: Oh, right, yeah. JAKE: So when we increment
it, it increments-- SURMA: It will be evaluated when
the closure is being executed. So that's actually when
it looks up the value. JAKE: Exactly. SURMA: I see. JAKE: So we're going to get
console.log of two twice. SURMA: Three times-- twice. [LAUGHTER] JAKE: We are
professional developers. I had to start at that as well. And it's like, I
ran this code early. Maybe, I don't know. SURMA: I just lost
all my credibility. JAKE: It's OK. You've got time to regain it. Here we go. But dun dun dun. Things have changed. SURMA: So intuitively,
I would think and hope that it wouldn't
change anything. But I'm guessing you set this up
in a way that it actually does. JAKE: Things have changed. SURMA: Actually, I think
you hinted at it because you said that previously, the
i was declared as a var, meaning it has been
declared within the function scope, which, if there is
nothing outside of this, this would be the global scope. JAKE: Yes, so let's
take a look at this. So what we have here
is an if (true). So this block is
always going to run. And we're declaring in there. We've got a var, a
const, and a let. That's logging them all out. The var is scoped to the parent
function or the global scope, whereas const and
let are I think the term is lexically scoped. SURMA: To the block. JAKE: To the block, right. So what happens here
with these console logs, this first one works. It will log one. But what we hit here with
this console.log(innerConst) and innerLet is that's going to
throw because it has no access to those. They are scoped within there. And like you say,
it's all blocks. And I thought the folks
don't need to know this, but yeah, you can just have
a bare block like that. I actually find
that really useful. SURMA: It can be. Since const and let, I
have used that every now and then to just declare that
this variables just need it for these three instructions. JAKE: Yes, especially if
you've got like a global-- if you're doing some inline
script in script tag, I just wrap it in
these to make sure-- SURMA: Or you want to
shell out something just for a specific number of lines. JAKE: Exactly that, yes,
that's what I've used it for. So back to this example. So you're saying same
as var, is that-- SURMA: So now that we
have caught up on that, the question now is the
let is technically-- I want to say
geographically, the let is outside of the block. But is it, though? JAKE: But is it, though? SURMA: It is a for statement. My intuition would still
be it should work the same. But I would also
understand if it didn't. But I don't know
what the answer is. JAKE: The ECMAScript spec
actually special cases this, this situation. So I agree with you that
geographically, this let statement looks like it's
outside the block of the for. But it does magic tricks. And this is where for
loops are just like, ho! This thing that
was really simple before is now actually
really complicated. So what you will
actually get here is a console.log of 0 and 1. SURMA: Interesting. JAKE: It creates a lexical
scope of each iteration. SURMA: So it's
literally a new i. It's not just the same
variable assigned a new value. It's a new variable. JAKE: It is, yes. It is a new variable. SURMA: Wow. JAKE: And there's a lot
of trickery in here. And this exposes some
interesting behaviors I've never really thought
about before about how for loops are specced. And one of them is-- so this ++ at the
end, the incrementer, I always thought of that as
being at the end of a for loop iteration. So do this, do this,
do that, and then-- SURMA: Well, that would work
if it's a new variable, right? Well, I guess it could. JAKE: Well, because this
log 0 rather than 1, we know that this
is not happening as part of that same iteration. And what actually
happens is this-- SURMA: No, it could be
just after the body, right? It could be still-- JAKE: Well, no, because
then this is a closure. So this is being logged. SURMA: Oh, you're right. Yeah, yeah. JAKE: And so this
is the weird thing. It exposes that the
incrementer runs at the start of every
iteration except the first one. I know, right? This is-- SURMA: Throw all the
elegance out of the window. JAKE: So the way
it actually works-- well, actually let's
introduce an extra thing. SURMA: So now the
question is does it copy the value at
the end of the body to the first value of the next
iteration and then apply the-- JAKE: I think you've got your
credibility back because that is exactly what it does. SURMA: Ooh, I mean,
it would have to because if you go back
to var land, it would-- JAKE: --behave like that. You can alter the
value of i within-- yeah, so what happens
is it does this bit, this special bit that it
treats as a declaration. It records all of the
things that it sets. So it has a
bookkeeping there of-- SURMA: Really? JAKE: So in this
case, it's i, just i. But you could declare many. It runs its check here. SURMA: Cool. JAKE: So it actually creates
a new lexical environment before it runs this check. And it copies the
value of i into it. SURMA: Because that
check can actually be a side effect, right? It can be all kind of stupid. JAKE: They can all
be side effect-y, which is the amazing thing. So then it runs the body in
that same lexical environment that we did this bit. So we've got our setTimeout(). We're logging. So at this point, i is 0. [INAUDIBLE] Then we're doing i++. So this is where we're
mutating i to be 1. And then that's the end of
that lexical environment because it then creates
a new lexical environment to do the next iteration,
which begins with this. And yes, you're right. It copies the value
from one into the other. For everything listed in this
section, it copies the value. So now it's taking
i, which is 1, into the new
lexical environment. We do our ++ on it. i is now 2. We do our check. If it's not less
than 2, so you-- SURMA: And we get a log of 1. JAKE: And we get a log of 1. SURMA: Even though
we incremented after scheduling the task. JAKE: Yes, because
it's still pointing at that instance of 1 that we-- SURMA: So just for the record,
for the people at home, don't. That is, if you have to
know these intricacies-- if your code relies
on these intricacies, most likely it's
a bad idea, right? JAKE: Well, I would say
that this is actually pretty intuitive because as you
say, it works a lot like var did. But it's doing so many
jumping other hoops and-- SURMA: I mean, I would have
said the same thing about var. If you rely on-- I mean, this is kind of
out of order, then, right? You do things to
a variable that's already been used beforehand. And you're relying on
the value-- these things, while possible, sh-- don't use them if you
any way to avoid it. JAKE: Yeah, if you
can async/await rather than something
like this, your code's a lot more sensible. But yeah, so the
copying between stuff only happens with
things declared there. So if you have another
variable in there, it is not copying that
between the executing context because it's not one
of the ones declared. It's got a special thing
where it deals with that. SURMA: So does const
behave any different? JAKE: Well, so you can
actually put const there. SURMA: Yeah, I know. JAKE: But then it will fail
as soon as this happens. SURMA: Oh, really? JAKE: Well, but it's constant. You can't really assign to it. SURMA: I mean, if it's a
new variable every time, let alone a-- JAKE: Well, no, because it
creates the new variable. And then you are executing this. So that's when the
mutation happens. So it is odd that the spec does
sort of allow that to happen. But it will only ever work-- SURMA: I mean, it could
still be a valid for loop if you have no code in your
incrementer bit, right? JAKE: So the spec still
has to deal with it. SURMA: [SIGHS] JAKE: So final one. SURMA: If you format
your code like this, I'm going to slap you. JAKE: [LAUGHING] The reason is because
I'm going to do this. SURMA: Why? JAKE: OK, so I am jumping
through some hoops here. All I'm doing is I'm
assigning 0 to i. But I'm just using
the statement. So I can stuff-- SURMA: Oh, I miscounted
the parentheses. I was like, wait, you're
storing the timeout ID in i. But it's not-- JAKE: No, there's a
lot of parentheses-- SURMA: So this is another thing
that people not necessarily know that you can-- if you want to concatenate
commands in JavaScript, you can use and and,
you can use a semicolon, and you can use a comma. They all have slightly
different meanings in how they concatenate and
when they abort to concatenate. JAKE: Yeah, that's why-- I could have used and and here. I didn't think about that. But it's-- SURMA: Just calling it as it is. This is not a function
parameter comma. This is a "concatenating to
individual expressions" comma. JAKE: Yes, because I can't
use semicolon because that's part of the for loop stuff. SURMA: I mean, you could have
used an anonymous function that you immediately-- JAKE: Ha. Look, I would have to make the
font even smaller to do that. So you've talked around
some of the problem. So my question is what is
going to be logged here. So I've got my i++ here. You've got your i++ there. What do you think? SURMA: I just want to leave. JAKE: Do you know what? I didn't know this until
I looked at the spec because I wanted to talk about
the complication behind a for loop. And I only discovered this
particular thing this morning. SURMA: So what I kind of
assume is that this parentheses expressions, which is the
setTimeout() and the "return 0," in a way, that is
being assigned to i once at the start, right? JAKE: But this i, it's going-- so this call here-- SURMA: So wait. You just asked me
what do I think. What is the actual question? The body should run once, right? JAKE: The question
comes down to which lexical scope is this code
going to be running in. Like we said, there are
many within a for loop. So what possible
mutations are going to happen to i before
this code runs? Because this for loop is
going to run to completion. SURMA: I mean, this is a task. This will run out
of the for loop-- [INTERPOSING VOICES] SURMA: --synchronous. So technically, you said this
i++ is creating a new lexical scope. But does it mean it's referring
to a different variable now or not? JAKE: I'm having so much fun. SURMA: I'm going
to say it logs a 1. JAKE: Logs a 1. And that is what I
would have said as well. But it is not correct. No, I seriously-- So here's the weird thing. It creates an
additional lexical scope for this initial
statement of the for loop. So it creates a lexical
scope, runs this line, and once it's run
that, it goes well, what are the values of
everything declared? And then it copied them
into a new lexical scope. SURMA: Oh, boy. JAKE: --to do the
rest of the for loop. To do this and then this. SURMA: So it will log 0. JAKE: Because it never gets
any of these mutational points. It's really weird. So you get so many
lexical scopes in just a simple for loop. SURMA: So wait, what we're
saying is don't use for loops. JAKE: Yeah. SURMA: Is that our
general advice? JAKE: No, use for-of. It's so much simpler. That is the story. You don't have to worry
about any of this. Just use iterators and for-of. So I've essentially just
wasted everyone's time. SURMA: Well done. Welcome to HTTP203. We are back. JAKE: Maybe we should
be one of those podcasts that has a little
jingle between-- SURMA: Yeah, we need
transition jingles. JAKE: (SINGING)
Da-da da-da da-da! Welcome back, listeners. We were talking about lasers. But we're now going
to pick up on-- SURMA: Today, we are sponsored
by Google Microkitchen. JAKE: This one's by
Google Chrome again. SURMA: [INAUDIBLE]. JAKE: Download Chrome everyone.
This was super helpful up to the “don’t use” admonition. All that hard work didn’t get put into designing those features just so we can turn our noses up at them. There is always a good use case or two for such things.