What do you want to learn?
Skip to main content
ES6: The Right Parts
by Kyle Simpson
Start CourseBookmarkAdd to Channel
Table of contents
The Arrow Function
Arrow Function Variations
Promises and This
Exercise 0: The Arrow Function
So that's my case for and against the arrow function. I now want you to practice. So I want you to pop open ex0, it's 0 because I was too lazy because I was too lazy to go back and renumber stuff. I inserted this just before the workshop. This ex0, big old hairy mess of foo bar stuff here, this is the file that you have. So you can know that there's a whole bunch of functions in here. Functions used in various different ways, functions used as IIFEs, functions used as function declarations, functions used as function expressions that are returned, functions used as callbacks passed to a map or a setTimeout. Some of them have a .this reference in them. A whole bunch of functions across this. Your task with exercise 0, go crazy with the arrow function. Right, start rewriting all of your stuff using the arrow function. And by the way, I didn't go over this, but the thing that people that love the arrow function love to do is they also love to avoid the curly braces at all costs. So let's say we had a function like this, let's say we had a function called foo that did an if statement, if x is greater than 5, return x else return 1, for example. Okay, very simple example. When somebody goes to convert that to an arrow function, now we have an if statement, you're not going to be able to use an if statement in the concise body, but we don't want to use curly braces, right? So the way we do this is we replace the if statement with a ternary because that's an expression and we can use that. So when you see people write that, they'll say var foo = x and then they'll start using a ternary, and oh by the way, we also need to write all of our arrow functions all on one line, for maximum readability sake, write them all on one line with no indentation or new lines. So x = x > 5 x or 1. Totally readable, right? Nobody has any trouble parsing that whatsoever. It's like the good old days of Pearl. So the point I'm trying to make is that whenever you start using arrow functions, you're going to want to pull out every kind of grammatical trick that you can to avoid having to write curly braces. I mean, I'm talking about putting like variable declarations as unused parameters in your parameter list, using the comma operator as a part of a way to put expressions, multiple expressions in one concise expression. I mean every trick that you can think of. So go crazy with exercise 0. Try to make as much of that. By the way, if you run this code, if I run this, what you'll notice is that it returns true at the end. So your code needs to preserve the test case that's built into it, it needs to continue to return true, if it doesn't you failed, okay.
Exercise 0 Solution
Let vs. Var
Closures and Explicit Blocks
When to Use Const
The question was about using const for immutable values like strings and numbers? I'm not 100% sure what the question is actually asking. It's Mark K, about 3 minutes ago. Okay, I think the question is where am I going to use const? The place where I think you should use const is where you are already doing stuff like this. If you did something silly like var PI = 3.14, you have my blessing to go change those all to const because those really are mutable values that you don't intent to reassign and if you want to put const on it, that's no big deal. But I don't think you ought to go do like const f = arrow function or anything like that because I think you're just inviting additional visual clutter that's not actually communicating what you think it's communicating. Okay. Yes? What's your take on using const for function names? Like let's say you're creating a utility function and you're calling it add sum or something? Like I just not, I would not do const add sum equals and then some function. I'd just make a function declaration. So there are relatively few places in my code where I have actual constants like this. But in those places I endorse using the const keyword. I don't think you lead off every one of your declarations in your code with a const, because I think you're inviting unfamiliar readers with your code to potentially have some visual stumbling blocks. Is there any benefit of using const on arrays then or objects? I don't think so at all. As a matter of fact, this is a little controversial because I've had debates with people about this before, but saying something like const x = array 1,2,3, let's say that this was a really huge array, like it was 100 megabytes of memory. And let's say that it was inside of some kind of function foo and I declared this thing here and let's say I set something up like, (Typing) so I make some reference to x inside of my function, I've closed over x, x is going to stay around for as long as that click handler is there, and in our program we're never unassigning that thing. But let's say there's some other condition within our program which is that we would like to be able to garbage collect that array because we're done with it. We'd like that 10 megabytes or 100 megabytes or whatever of memory to be recollected. Normally you would do something like x = null, that's no longer allowed when you use a const. So as a matter of fact, there are places where the const could actually make it worse, you have to fiddle around with your garbage collection sorts of things. In this particular case, since it's an array, you could have done something like x.length = 0, but if it were an object you wouldn't have that, you would to for loop over them and delete all the properties or something like that, super awkward. So there are places where const can actually trip you up, just like there are places where let can trip you up. So I would recommend using the const in the places where it's actually justified rather than just preemptively using it everywhere and then getting tripped up. This is sort of a follow on using const with Object.freeze. I don't think it helps at all to add const and Object.freeze, okay, I don't think that does anything extra. If you want, great, but I don't think it helps anything at all. I think because that comes at the beginning of the line, it's already signaled somebody to get confused as to what's going on.
Exercise 1: Variable Scoping
Let's open up exercise 1. You notice that I've created some functions here and I've created some for loops and stuff. Here's our test case at the bottom, it needs to assert true, which is the case that the x value of 2 multiplied by 2, so we're saying that 4 needs to be the same thing that's returned if I call the function in this fns array at position 4. So in other words, I'm going to loop over from 0 up to 5, not including, and I'm going to add functions to an fns array. Each one of those functions needs to close over some variable, such that when the function is called it returns that value. Okay. So looking at your IIFE here, looking at your var statements, figure out which ones of these should be made into lets and const and refactor this code and set yourself up correctly with your closure. It should only take a few lines of code. So we'll just take a 3 minute break and then we'll talk about the solution.
Exercise 1 Solution
So let's talk about exercise 1. Here we started off with a var x = 2. We can kind of see in the scope of this program, that's sort of being used like a constant, so that's a place where you could make the argument that it should be a const. Fns is definitely not going to be treated as a const, in the sense it's not going to be treated as a constant, even though it might be reassigned, we're going to be adding to it, so I think it be super confusing to declare fns as a constant and then later in the code start adding to the contents of it, okay. You can, but I don't think you should. So that's why we're going to go ahead and declare fns as a var, because we're telling someone hey this thing is going to be reused. And I use a var here instead of a let, again, because fns is going to be used across multiple scopes. Alright. Now in the previous example I had an IIFE, and the only purpose of this IIFE was so that I could make another var x, which was sort of my constant in terms of how high up to go. And there would be a collision there if there wasn't a scope. Well I can do that with a block, which I'm doing here, lines 4 through 10. And again, I can use const here because x is actually sort of being used as a constant in that sense to tell us what to run the loop up to. I'm going to do a for with a let keyword because I know I've got closure involved and I'm going to want to close over an individual i for each loop. I'm going to add these functions if into the fns array and just simply close over that i variable, and that preserves my test case, I end up getting true. Questions about exercise 1? Yes. A question from before, but in a more broad sense, Justin had a question about why ESLint is starting to throw errors for var? Yeah, because there's… I want to save it for after because there's a crowd of people that are under the impression that let is the new var and that var is a code smell, so now they have default linting rules that tell you don't use a var under any circumstances. I just think that's a bunch of nonsense, but there are some people that believe that.
Default Values and the Gather/Spread Operator
Gather and Spread Operators, Part 1
Gather and Spread Operators, Part 2
But here's something cool, because this is just a regular arguments list, we don't actually need to add that onto the array, we can just literally put the value 42 right there, we don't even need to modify the array. Just say 42, ...args. I think that's a whole lot more declarative form, as opposed to the imperative form over on the left. What we mean by the difference between declarative and imperative is this specifically, don't miss this, the difference between declarative and imperative is that imperative includes all of the implementation details of how, but most of those implementation details of how are not the things that the programmers should have to think about at that moment. So what we're doing is taking those details, abstracting them away into the engine inside of a feature, so that what's left is the stuff that we should focus on. How many of you have heard before that the purpose of abstraction is to hide details? Anybody heard that? That's probably how you would describe it. I don't agree. Abstraction is not about hiding things. If you go back to the original definition of when the idea of abstraction was created, what were they trying to do with abstraction? They're not trying to hide stuff, they're not trying to create black boxes. You know what the purpose of abstraction is? Focus. The purpose of abstraction is to take two things that are interwoven together and by interwoven together it makes it hard to reason about either one of them, to separate the two so that each one can be individually focused on, depending on what you need to focus on. So what we're doing is we're taking the thing that's going to happen and then how to do it and teasing those two apart with a declarative form, meaning that we can focus on the stuff that actually matters. In this case what matters is that we want to add 42 on as the first argument and pass everything else along. That's the thing that matters, all the other code over there was just a distraction to get us to that point. So the declarative form here helps us to make more reasonable, understandable, and readable code. It removes the details that distract the reading. Are you following me? Again, that's the narrative we want to be looking for. Every feature you look at in ES6, there's 1000 other features in the language that I'm not talking about today, whatever choice you use of features or whatever, I want you to evaluate it based upon that. Is it helping me to communicate more clearly? Sometimes that will mean that we create shorter code like we're doing here, but there will be times when the most communicative ES6 code is actually longer than the other one. So it's not about saving characters, it's about communicating more clearly. So you said args is a keyword here, it's a new keyword? No, no, no. The ... is an operator. Here I can call this whatever I want, I can call it foo, I mean bar, if I wanted to. It doesn't matter what I call it. Okay, thanks. I have called baz I guess. Yeah, I just, by my convention I use args, some people used to use rest because they like the rest operator usage. The ... gathers up everything that's not already accounted for, so if we had already accounted for say one of the arguments with the named parameter, ...args is going to be everything else, not including the thing that's accounted for by x. Okay. So here we would, this would have the effect of essentially throwing away whatever the first thing was that we passed in and replacing it with 42. You see how I did that? If I did the equivalent, I'm not going to waste your time, but if I did the equivalent, I'd have to do a slice and slice off the first one and then pass in a new one or whatever. It's just, it's syntactically done for us. Throwing away the parameter x that we don't care about. The question was, can we have default params with gather? So you can do defaults here, of course, you can do defaults there all you want. I think it makes complete sense that we ought to be able to default that args array to something like, for example, 1,2,3. It's not included in the language and when I ask them, can we add that? They're like, you're a bad person for asking that, so stop asking. Which is why I don't participate in the spec lists anymore because every time I come to them with an idea, they shoot me down. So I don't know why we can't have default values on these. The question was a little bit different. Was it? Sorry. They're asking if the ...args can be used as a default value? No. Sorry no. So the question was, could I say something like y = ...args? So you have to think about it, in that particular case, we are back to a value semantic, not an assignment semantic. So we're back on the value side of the equation, so ... would actually be spreading out the array. So technically you could do something like ...x, no that won't work, never mind, you can't spread out in a non-comma list. Forget I said that. So no, you can't use it like that. Sorry. Okay. I have a question. Yeah, sorry. Can you spread the keyword arguments or does it have to be an actual array? There's a proposed feature, so arguments is an object and we can't use the ... operator by default against regular objects right now. There is a proposal that is now I think stage 2, to add the ... operator, both gathering and spreading, to objects as well. It's not in the spec yet, but it's working its way through the process, so sometime over the next year or whatever we'll probably see that land. So they would be array-like objects or objects in general? I guess I don't know how that would work with like a plain object structure? Well it depends on which line of code you're talking about. If you're talking about line 1 or if you're talking about line 2? I'm talking about spread, so line 2. So if I had the ability to spread and I did this, there's no more object at all. What's getting passed along is the individual values in the arguments object. My question was, can you apply the spread operator to arguments because arguments isn't an array, it's an array-like object? Well not yet, but when this lands in a year, or however long it takes to finish, you will be able to do ... arguments. So I must have missed it, is it already approved or is it still...? It's in stage 2. Okay. There's 4 stages, actually 5 stages, and it's at stage 2. So it's working its way through. There's, I'd say, a better than 50% chance that it will land, but we don't know when. One of the complications there, just because you're asking, one of the complications there, spreading out an object is not such a big deal because you can think conceptually, oh just loop over all of its properties, but then there's questions like does that include only owned properties or what about properties that are delegated on the prototype? Should it by default give all of them? But really the gather is the question with objects. What does it mean to gather a bunch of values in? How do we figure out what the property names ought to be when we gather into an object? So there is some weirdness that they're trying to work out, but I think it's got a pretty decent chance of eventually landing.
Using the Gather and Spread Operators
There are other places where, besides parameter lists, where this gather and spread can be used. So another place is within the context of array literals. So let's say I have x is equal to array 1,2,3 and y is 4,5 and what I'd like is to define a z which includes the content, includes a value plus the contents of x, plus the contents of y, plus another value. So I would do that by saying array of 0.concat and I'd say x,y and then another array with some other value like the value 6, for example. Alright. Anybody ever used array concat before? So now z would have 0,1,2,3,4,5,6 in it, okay. So any place where I find myself using the .apply and the .slice, those are places where the ... helps, but also places where I see myself doing .concat, that's another one of those smells, like oh maybe this is a place where ... can help me. Okay. So this is the imperative form of doing that. The declarative form, if we start out again with the same array, var y = 4,5, and we have var z is equal to and now we just make an array with the value in it, we spread out x, we spread out y, and we add another value into it. So arrays can be spread out in function calls, like we did before where we were passing a value along to bar, they can also be spread out inside of object literal, I mean, inside of array literals. Alright, so we can use them inside of array literals as well, which is nice. So kind of putting those things together, I can have a foo that included this, if I wanted to pass those all along. I could put those all together and then ... it, so I could make an array that I then spread back out, or I don't even need to do that because those can all just be passed individually. All the same thing. So array gathering and array spreading can be really useful for us. And depending on how you do this, you can kind of play some interesting tricks, which I'm setting you up now for the exercise that we're about to do. You can play some interesting tricks where, for example, let's say I did this, (Typing) what I've done there is effectively throw away the first two from a, so it's kind of implicit, it's kind of declaratively slicing off the first two from a, leaving the remainder of a and the remainder of b, but all gathered together into a single array. Okay. So we can use different kinds of tricks to do the declarative form of doing all that imperative slicing and concating and all that other stuff that we might do. There's a couple questions, you should probably read them yourself. VJR is asking about the gather spread operator performance. Alright so to answer a question about performance, this is going to be super annoying to some of you probably, but I'm going to tell you that most of your intuition about how you ask about performance, you're asking an invalid question because the premise of most of those questions, including this one, the underlying premise is is it faster or slower than what I, than not doing it at all? And that's not an apples to apples comparison. What we really need to do is an apples to oranges, I mean that's an apple to oranges, what we really need to do is an apples to apples comparison, which is to ask - is it faster or slower for this ... thing to be used compared to all the slicing and concatting that I might do? That's the real question that we might ask. Now, the answer to that question is, it doesn't matter whether it's faster or not, what matters is that this is a form that the compiler can recognize because it's a declarative form, which means that the engines have the opportunity to optimize it where they don't have the opportunity to optimize the other form. That's the thing that matters. Okay. That's yet another benefit of using a declarative form. The compiler has the ability to see in the future. I don't know whether they do, but I don't care because I'm not an engine internals person. But what I do know is that the engine has the ability to recognize that syntactically and say, I know what they're doing, they're putting some stuff together and put in a super hyper optimized instruction to make that happen. They cannot do the same over here. The engine can't see what you're trying to do there up front and make a super hyper optimized form of it. Okay. So it might sound like I'm dodging the question, but what I'm really trying to get people to realize is that the value of ES6 is not about whether it is or is not more performant. In general, in the long term, every declarative form will be at least as fast as the old version and probably much faster. But whether it is today or not slightly slower or slightly faster is an unquestioned benefit in terms of code readability, and that's where I think our focus should stay. Does that make sense? Question about how this works. Um, what you have on the right side up there. So I think what's going to happen is x and y are going to be the first two elements of whichever of those arrays a and b got passed in, correct? Yeah, if we made an a and a b, like for example, a is 1,2,3 and b is 4,5,6. X is going to be 1, y is going to be 2. And if x, array a only had one element, then x would be the one element, then y would be the first element of b? Yep, exactly. So there was a response in terms of readability. Somebody trying to debate me on readability? Yeah I think so. I don't want to get into debates on it. I'm presenting you all sides and it's up to you to decide what you think about readability. Personal question, on the notion of the gather operator, what if a was a scaler? Say that again. What if a was a scaler? You can't use a ... operator against a value, unless, and here's I'm going to tell you something, but I'm not going to explain it until the end of the workshop. At the end of the workshop we're going to talk about iterators, the ... operator spreads out anything that has an iterator, which is called an iterable. If a value is iterable, it can be ... spread out. Okay. So arrays, by default, are iterables because they have an iterator built into them as of ES6. Another thing that's a default iterable is a string. If I ask, from the individual characters, if I wanted to get like a real array of the individual characters of that, the old school would have been to say something like string.split and now I'd get an array out of it. But now we can just treat that string as an iterable, because it is, and ... will actually spread it out. So if I said something like ... string, now that value is an array where each element is one of the characters from the string, so any value that has an iterator. A primitive, like the number value, would not have an iterator by default, but at the end of today we're going to do an exercise where we can add iterators to our own custom values.
I think that's good on our gather and spread, so let's open up exercise 2. And this is, we will finish exercise 2 and then take our lunch break. Okay. So go ahead and open it up and I'll orient you to exercise 2 and then give you, we'll take 5 minutes on exercise 2, 4 minutes, it's a short exercise. Okay. Same kind of thing that I was just talking about before, the kinds of tricks that we can play with spreads and gathers and with parameters. The spirit of this exercise is that when I call the bar function, which is giving me back whatever comes back from foo, that's going to give me an array that if I join those together comes to this string. In other words, I'm expecting an array back with the values 2, 8, 10, and 12 in it. And the starting values that I have to play with are these two arrays. So in some way, shape, or form I want to transport those values into foo and in some way, shape, or form I want to declare foo and return something from foo that ends up resulting in an array that has 2, 8, 10, and 12 in it. Do you follow that? So using the same sort of stuff with gathers and spreads that we just talked about, see if you can declare that code.
Exercise 2 Solution
So let's talk about solution for exercise 2. We have the a1 and the a2 arrays. If we want to pass those in, we can just spread out a1 and a2. And then we have the foo function which we want to define. We want it to return something, but we're going to declare the parameters in such a way that it does the work for us. So we basically want to account for, we want to keep the first one, but then we've got two that we don't. We don't care about the value 4 or the value 6. So essentially those are ones that we're going to throw away. So we could name them y and z, or we could name them something else that indicates that we're going to ignore them. Right, we could them ignored 1 and ignored 2 if we wanted to. But then everything else, we do care about. So we can say ...rest or args or whatever you want to call it. So now we have an x value and we have the rest value and we want to put those two together. So we can say return, make ourselves an array that includes x and everything else. Now the foo name here can throw people off because what would foo be? You'd give foo some sort of a name that made sense. For example, if the whole purpose of this function is to ignore the second and third arguments, then you might call this thing ignore2 and 3, so something like that, you'd give it some sort of reasonable name that described what it was doing and now this would be a utility that, and ostensibly, this would be something that you were doing on a regular basis, so now you can use this facility to pass in a set of values and have it ignore just the 2 and the 3 and keep everything else, right. So don't get too tripped up when you see tricks like this and you see me only using foo and bar, think about it in terms of how would I use that kind of facility in my own code, what would I call that utility? that sort of thing.
Destructuring and Default Values
So if we know that we can receive assignments without declaration, we can also do an interesting little trick, this is just a little side note, it's not something I do an awful lot, but you remember earlier when we were talking about using block scoping to do that whole like swapping thing? Remember we did left tmp = x, x = y, y = tmp, remember that? We can use array destructuring to do the swapping. Let's say I had an x that was 10 and a y that was 20 and I wanted to swap the two. I could do that whole block thing that I just showed or what I could do is say, an array destructuring with x and y as my pattern should be destructured from an array that has them in the opposite order. Of course that's not limited to two, you could do as many of those as you wanted to, right. While we're on the topic of using this array destructuring stuff, if we had an a that was an array like that, we could say, for example, (Typing) so now we're making an array, a composite array, I'll use spaces just to make this a little more readable, we now have an array on the right hand side that has 0 through 4 in it, right. And over here would say I want to throw away the first two of these, we're going to have to make variables to catch those, collect the rest of them right back into a. So that is the effect of throwing away 0 and 1 and now a is going to have 2, 3, 4 in it. Okay. Remember our whole usage of ... operator, it also works with our destructuring. So now we see the ... on both sides, one on the right hand side it's doing spreading, on the left hand side it's doing gathering. And we could just have a single variable here, like that or underscore or whatever other one you wanted to use to say this is just, I'm just dumping these, I don't care about them. Okay. But you don't even need that because array destructuring allows you to do empty slots. So now if we ran this example, our a would be an array that had values 2, 3, and 4 in it, 0 and 1 would have been dumped. Okay. So we can use our pattern destructuring in even more, I don't necessarily want to call it a tricky way, but we can use the capabilities of it to take on tasks that would have been a lot more ugly to express in the imperative form. So I take it if you transpiled that it would just give it some random variable names that never get used again on those two that get tossed out? Oh it just slices instead? See they're doing all this analysis to figure what's the simplest path, fewest characters, most performant to get it. So you might say, oh this is how I would do it, I'd make these temporary variables or whatever, and they're saying, we've already analyzed it and figured out this is the best way to do it. So with the empty slots, does the white space matter? Or is it just the commas? Yeah, I do that for readability, it doesn't matter at all. (Waiting) (Typing) There's the array with 2,3,4 in it.
Nested Array Destructuring
If we can destructure a single level of arrays, we can obviously destructure nested arrays. So I could, it might be the case that I would have in this position an array that had 4,5,6 in a nested array, okay. I'll spread that out so it's a little easier to read. Okay. So now at the fourth position, we actually have an array with 4,5,6 in it. What is args going to have in this particular case? Tell me what the structure of args would be? The array 4,5,6? No. (Waiting) An array where the first item is the array 4,5,6. Exactly. Args would be an array with one item in it, the item in that one position in args would be the array 4,5,6. Okay. Do you follow that? Alright but what if I didn't want that? What if I didn't want that thing as one big array? What if I wanted 4, 5, and 6 individually addressable? Well I can do array destructuring right inside of my pattern, a nested array destructuring that says make d the first one, I don't care about the second one, and make e the third one, for example. As long as I have those as locations that I could assign to. So now d is going to be the value 4 and e is going to be the value 6. Array destructuring uses position, just like regular arrays use position. When we talk about object destructuring, you'll see those use property names to do the mapping, but here there's an implicit mapping from 0 to a and from 1 to b because we're using the position instead of the property name. (Waiting) So now you can start to see, you can start to visualize that if I had a really complex structure coming back from an API, I could have a nice little nested list of all the places, of all the stuff that I cared about, nesting down an array, inside of an object, inside of an array, inside of whatever, and then I'd have my two or three little variables that I cared about, like first name and email address, and those are the only ones I care about, and the pattern becomes a self documentation. How many of you have ever done before, have some kind of API that you're consuming and what you do is take a capture of that API structure and put it into your JSDoc comment? Because you need to document what the hell you're expecting to come back from this function call, right. Well now your pattern becomes your self document. You document what you're expected by doing the destructuring pattern according to what you're expecting to get back. No need to duplicate it in a JSDoc comment, it's just right there in the structure. Okay. That's what I meant when I said earlier that destructuring becomes a thing that pays off bigger than just itself because now it starts to teach you something about the bigger aspect of what's going on in the code. The time spent for you to learn how this works pays off far beyond just that one line of code. Question about array and object? Yep we're going to get to mixing them in just a little bit. So can you talk me through the, a moment ago on line 11 you had ...args, Yep. and I don't think I ever got how that would come back as a single element within an array. Can you talk through that one again? Yep, let's go through this one one more time. ...args is at the top level of our array destructuring pattern, which is saying any items that are left in my array should all be collected up into an array called args. What is the only item that's left in our array? It's an array value? So there's going to be a single array holding that one value at position 0. Does that help? Oscar has a question about why do you need all those vars at line 5? Only because I'm not putting the var right here. Remember, I switched from doing declaration syntax to doing general assignment syntax. So if I go back to the declaration syntax, it's a little easier for us. Okay. Not that I would encourage doing it, but does it work if you don't have a var and you treat those as globals? Or will it not allow global assignments? No, it will allow global assignments, I mean if you don't have a var there, it'll allow a global assignment, but then you have the question of if you're in strict mode, which you should be in strict mode, then you're going to get an error because that's not allowed in strict mode. (Waiting) Okay, so we can either account for one of the values here or we can destructure it, or we can collect it with a ...args. But we can't do both. That will be different when we get to objects, it will be possible to double account for something with objects, and we'll get to that in a moment. But with arrays, this position is either ...args or it's a variable called d or it's a destructuring pattern that's broken down further. You've got to pick one of those three. Okay. (Waiting) Alright another thing, before we move to talking about objects, is this idea that, make sure I have the best way to illustrate this. (Typing) What's happening here, if we went back to that simple case, is we do have a declaration that's occurring, but we also have, and actually this is going to be a lot simpler if I do it this way, so I'm going to go back to that case where I'm just doing destructuring assignment without declaration, just keep this simple. What's happening here is that there's an assignment expression happening and the end result of the assignment expression is itself a value. We already saw that earlier in one of our exercises where I assigned an arrow function to a variable and then the arrow function came back from the assignment. So here we have, what do you think would be the value of x? First off, do you think this is valid syntax? Yes or no? And by that I mean, convince yourself that it is valid syntax. Why is it valid syntax? Why is this, for example, not an array? Because it's still an assignment target. Because it's still on the left hand side of that assignment. So it's still in an assignment context. Now this assignment happens and some value comes back. Many people think that x will be the array of 1 and 2. Because they're thinking to themselves what comes back from that assignment is only what I captured in my destructuring, but that is the wrong thinking because that is thinking that bracket a, b is an array. Bracket a, b isn't an array, is it? It's a pattern for how to break down an array. So what do you think actually comes back from a destructuring assignment? The whole array, right. So x is actually going to be the whole thing, 1, 2, 3 with an array and 4, 5, 6, that's what comes back from the assignment. Okay. The destructuring assignment is not creating a new sub-array of just two items, it's breaking down the array according to a pattern. That's a subtle, but really important processing signal. So in the same vein, you could switch x with the a, b destructuring and you'd still get the same result, right? Because x would be the entirety of foo. If you did that, now your declarator would apply to var a and b and this line would be redundant, but x wouldn't exist, so you would need to declare x. But otherwise, same result, correct? Same result. Okay. But I want to go back to the case where we don't have the declarator involved, I want to go back to this particular case because I just said, and that's what prompted me to go off on this little rabbit trail, I just said, oh well you can either account for a position using a lexical name or using a ... gatherer, or using a nested destructure, but you can't do multiples. Actually, you kind of can because you can chain multiple destructuring patterns together since every destructuring assignment returns the original full array. So I could say something like a, b ...args, which is going to collect 3 and the array 4,5,6 in, and I could also have another destructuring. I'm going to start breaking these onto multiple lines for readability. Okay I could also have another destructuring which doesn't care about any of those first ones, but starts breaking down 4,5,6. Okay. Let me try to use some white space here to help make this a little bit more readable. (Typing) So let's go from the bottom to the top, first the foo function returns us an array. We destructure assign a to the value 1, b to the value 2, and collect into args the value 3 and the value 4,5,6. You with me? Then we say the whole array again is returned and then we do another destructuring, which throws away the first three and then it goes into that 4,5,6 and it says c should be the value 4 and d should be the value 5. So it is possible to chain multiple destructuring patterns together to accomplish that trick of doing accounting for the array in multiple ways. Any questions about array destructuring? When you do multiple levels like this, do you have to have the, I'm assuming you need var a, b outside of the? Yeah, we would have had to do all our _____. The var is only going to apply to the left most pattern, so we could have done var here and then had a var a,b and args, or we could have done all of them, which is probably what I would have done, and had no declarator attached. Yes? So a request for more concrete function and var names. I know why you do this, but I thought maybe you'd want to. I understand the request for more concrete names. I don't really want to get into the full reason why, but I just want to tell it's not because I'm uncreative or lazy, there's a reason why I reach with the foo and the var and there's a reason why I try to make your exercises not necessarily use too much of the foo and the var. So we're doing both and teaching the concepts with the simple stuff and then we try to apply it with less weird stuff. That's not always the case, a few of the exercises have to use those, but. What about args? Is that, do you still think of using that here? Because to me it's like args might be used in a function. Yeah, maybe there's a better name for that. In that one I would just, you know, could you call it some letter? Whatever. Or vals, maybe that's a better one. Yeah I was just stuck in the mindset. Now I'm happier. Okay. In particular, when we get to the end of this discussion of destructuring, which we've still got a little ways to go, but when we get to the end of the destructuring, the exercise that you do on destructuring is a more concrete example, it's not a foo bar example. Okay.
So let's reset back to the case with objects and a lot of this is going to start to be more self explanatory now because you already know what to expect because we've seen it with arrays. A lot of the same things are going to be true. We could have done this as tmp.a and tmp.b and tmp.c. Okay, that would have been the pre-ES6 equivalent. Now what we can do is take this function and we can do an object destructuring, so I'm going to do an object pattern with curly braces instead of brackets. And in my object pattern I'm going to say, I want the a property to be targeted to an a assignment location. Okay. And I want b to go to b and I want c to go to c. In the case where our target and our source are the same, meaning we don't need to do any alias or renaming, we do not have to specify them twice. We can just simply say a, b, c. It will assume that means go get the property of name a and assign it to a lexical variable called a. Okay. But if we wanted to, for example, reassign the b property instead to a variable called capital X, we now have declared an a variable, an x variable, and a c variable, but not a b variable. Are you with me? Oh, so the X is actually the target, Correct. not the source. Correct. Interesting. Yep. The way to remember this is that with object literals, the property name is always on the left, with object destructuring the property name is always on the left. These can, of course, also have default value assignments. So the equivalent from over there, we just add on the equals, right. With or without the colon, we can add on the equals. (Waiting) Same thing applies here, if we return something that was not an object, could have a failure here. So our little guard is to put an empty object to be destructured in the case where something falsey came back. Of course if our pattern doesn't account for a property, no big deal, that property just doesn't get worried about. If our pattern accounts for a property that doesn't exist, we end up getting a variable that's just undefined because we accessed a d property on an object and it didn't have one. Question in the chat is - shouldn't X and b be switched in order? No, this is how it works. Property name is always on the left, that's the easiest way. We can get it in nuances of grammar and there's ways to twist your brain into thinking it's backwards, I have found from my teaching experience and from my personal dev experience, the way to keep this straight in your mind is to think whenever I make an object literal, the object property is always on the left and then there's a colon. When I make an object destructuring, the property name is always on the left. Now property name and value have, or target and source, have different meanings, those get flipped semantically between the two contents. But the thing that stays the same and the easiest way to keep this straight in your brain, proper name is always on the left. That makes sense once you think about the fact that that's, you know, at least in this case, you have the var there and that's your target, because that's why I asked at first because I was like, well how can you have a, b, c, on the right hand side when those aren't like existing properties, and the answer was you don't, you're creating those. Yep. Otherwise I would have expected it to be like the string a or something to grab that property. Yeah. So if I wanted to use that destructured object, I would just give it a name and then I could use it for whatever I need it for. You mean, you wanted to capture the object and also destructure it? Let's say I want to destructure the return value from foo and then store that in a variable to use later. Okay that sentence doesn't make sense. Once it's destructured, it's no longer an object, it's a bunch of individual assignments. You could capture the actual object that came back from foo and then also destructure it, but you can't do it the other way around. So if you wanted to do the former of those, which is to capture a reference to it, you could have an obj somewhere and you could say... So now obj is going to be made a reference to the object and then that object reference comes back and gets destructured. So now we have both the object and the destructuring assignments from it. Is this similar to the array destructuring where you could technically switch those? Like, does the object destructuring also return the full object? Yes they do. Okay. What's also true is that if you switched them, your var is going to apply to the first one and not the second one, but yeah. Destructuring both arrays and objects always returns the full thing that's being destructured. I don't suppose there's anything built in right now that's similar to a rest for the arrays where you could get all the other properties of the object? That's what we're talking about when we say there's a stage 2 proposal for object spread and object gather, this would be where you'd want an object gather, but then what does exactly that mean? There's a lot of weirdness, especially when we start thinking about prototypes chains and stuff like that. What if I gathered into an object that already has a read-only property higher up in the, there's all kinds of craziness about it, but they're working through that spec process to figure out whether or not it's something they can add, and I think there's a good chance it lands at some point. Alright I'm going to take off the object one just so we have a little less noise here, but.
Nested Object Destructuring
Destructuring and Function Parameters
Let's go ahead and open up exercise 3. We sort of have almost the same thing, expect in this case there's a little bit of foo baring going on, but we have almost the same thing going on, so let me orient you. What we have is a check function that's going to pass in an object, but that response function is receiving an object. Okay. So stick with me. What you want is you want, right here, you want to do some object destructuring, and right here you want to do some object restructuring, Exactly like I just did for you before.
Exercise 3 Solution
Let's talk about ES 3 fixed, just to keep things short and sweet, I will just show the fixed version, but I'll talk you through it. So what I decided was to do object destructuring on the parameter that comes into the response because, remember, the response is receiving an object, so let's go ahead and just destructure it while it's coming in. This lets me not have to do any let or block scoping to contain those temporary variables because they're just going to be contained within the response function itself anyway. So I'm going to have a foo variable that defaults to whatever defaults.foo is and a bar that defaults to defaults.bar. I'm going to grab baz as is, which is just an array, and then I'm going to do these destructurings down inside of bam. Don't forget to put on your default objects so that your destructuring gracefully fails, okay. Now to do the restructuring, I can use those concise properties because I named them all exactly the same as the properties, so it's just foo, bar, baz, and then the qux and qam are inside of the bam object. Again, this is one of those places where I probably wouldn't keep all those defaults in a separate object if I didn't need to. I'd probably just go ahead and put those defaults right here, for example. Yes? Question from the room. Mike is asking - I don't know if I missed this, but in object destructuring, if there is a listed property not on the object, but up its prototype chain, will the value from the prototype be used or will it be undefined? Good question. So let's assume I did something like this, (Typing) so I'm asking for the a property on obj. If we defined obj like this, we know it doesn't have an a property, so it's going to ask for that a property, if it doesn't find it, it's just going to get undefined. But if we made object, if we made that property by doing a linkage to another object which did, and then as asked for the a property on it, if we did that manually, if we said obj.a, what would we get? Obj doesn't have an a, but it's prototype linked to an object that does, so it would find that a property, right? Same is going to be true in destructuring. If we ask for an a property on an object, but it doesn't have it and yet it finds it on the prototype chain, it'll pull that one out. So again, just think of destructuring as a declarative syntax for doing all those assignments and then the behavior of those assignments is exactly the same as if you did it manually. Any questions about destructuring? If it's still like 80-90% fuzzy, don't worry, that's completely natural, it's going to take a little bit more practice. But of all the things I'm teaching you, this may not be like the low hanging fruit stuff like ... or the ... operators and defaults, those are low hanging fruit, those are easy. This is not low hanging fruit, but it also has the biggest payoff, in my opinion, in terms of code readability. So it's worth the effort.
Concise Properties and Methods
So, let's talk now about an extra feature that's included with interpolated literals that is probably not terribly self obvious what this is or what it's for. It is possible for me to put an expression directly in front of the interpolated literal, and that expression will act as essentially a function call. So, you're thinking, well, what is the foo function? It would be a function that we would have to define and you might assume that that function would receive the interpolated string literal as its argument. It's not really quite that. It's sort of like that but not quite. So, let me talk to you about what this foo function would do. This foo function is going that receive as its first parameter, as its first argument, by convention, we like to call it strings. I mean, you can call these whatever you want, but by convention, most people call it strings because what it is is an array of all of the string literals from here. So, it is going to be an array that includes that string, and that string, and that string, and that string. So, it's going to have four string values in it. It's an array of four string values. The next thing that we would get, obviously, we're going to get these values in, you might think we would get a values array. For some crazy reason, they decided not to collect all the values up into an array, so what you actually get is like value1, value2, value3, all the way out to however many values there actually were, which is stupid and nobody wants to deal with those individually, so we all just collect them open to a values array using the ... gather operator. So, virtually, all of these, these are called tag functions, virtually every tag function you'll find, the signature will look almost identical to that, called strings, and then ...values. Obviously, you can name them whatever you want, okay? But what's the purpose of this function? Well, I can do really evil stuff like, "I am evil"; if I return a different value like for example, that string, and we ask what's in message, what do you think's going to be in message? The string, "I am evil," it has absolutely nothing to do with what we passed in. I don't think it's a good idea, but you can. The point I'm making is that the tag function serves kind of like a pre-processor. It's almost like a macro, it pre-processes the string before it's finalized. Which is going to allow you to do some actually very interesting things. Before we talk about the interesting kind of stuff that we can do with them, let's just think about what's the very simplest approach for me to construct these things together. For example, let's observe that the strings array is going to have four values in it, how many values are going to be in the values array? Three, right? Four strings, three values. Even if we had an expression at the very beginning of our literal, there would still be an empty string in the strings array there, and even if we had an expression at the very end, there would still be an empty string there, so it will always be the case that the strings array has one more value than the values array. That is an invariant, it will always be true. There will always be one more string then there is a value. So, you can probably realize if I had four strings and three values, what do I need to do to them? Loop over them and interpolate them, right? Concatenate them together. So, the pass-through version of foo, that is the version of foo that acts as if it's not even there is to simply loop over the strings array and interpolate in the values, totally untouched. So, let's write that logic. We'll have a string that we start out as empty, we do a for loop, for var i=0; i