JavaScript for-loops are… complicated - HTTP203

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

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.

👍︎︎ 1 👤︎︎ u/tehpsalmist 📅︎︎ Sep 09 2018 🗫︎ replies
Captions
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.
Info
Channel: Google Chrome Developers
Views: 96,048
Rating: undefined out of 5
Keywords: For-loops, for-loop, for loops, for loop, python for loop, php for loop, javascript for loop, what is a for loop, for javascript, for loop js, for js, for statement, web developer, for statement python, looping statements, python iterator, for loop c programming, web developers, web development, chrome, google chrome, google chrome developers, chrome developers, web, developer tutorials, google, google web development, google web developers, GDS: Yes;
Id: Nzokr6Boeaw
Channel Id: undefined
Length: 14min 16sec (856 seconds)
Published: Tue Aug 07 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.