What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Rapid ES6 Training
by Mark Zamoyta
Stay up to date with the new syntax and features of the latest version of JavaScript, ES6. You'll learn about features like rest and spread operators, the new 'symbol' type, as well as Iterators, Generators, Promises, and more.
Resume CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Recommended
Course Overview
Course Overview
Hi everyone, my name is Mark Zamoyta and welcome to my course, Rapid ES6 Training. I'm a software developer consultant in Santa Barbara, California. I've worked with every version of JavaScript since its inception and I'm excited to teach you the latest version ES6. The formal name for this update is ECMAScript 2015. But much of the developer community has stuck with the original name ES6. Google's latest Chrome browser supports 96% of ES6 features. Firefox and Edge are close behind and catching up. There are also transcpilers to get many ES6 features working on Internet Explorer and mobile browsers. Now is definitely the time to start coding in ES6. This course will rapidly review all aspects of this latest JavaScript update. We'll cover new syntax which makes JavaScript look and feel more modern. We'll see how to structure of JavaScript code to make use of modules and classes. Almost every built in JavaScript object has been extended, so we'll see what's been added. New objects and APIs include collection classes such as Map, WeakMap, Set and WeakSet. We can now work with Iterators, Generators and a new primitive type called Symbol. We also have a built in Promise class. And there are two new APIs for meta programming; the Reflect API and Proxy API. By the end of this course you'll know and be able to use the new syntax and features of ES6. Before beginning this course you should be familiar with the prior version of JavaScript, ES5. I hope you'll join me on this journey to learn ES6 with the Raid ES6 Training course at Pluralsight.
Introduction
Introduction
Welcome to rapid ES6 training. My name is Mark Zamoyta, and in this course, we'll take a thorough look at the latest version of Javascript. The ECMAScript Language Specification was officially updated last year, in 2015. The release had been called ES6, but had a sudden name change to ECMAScript 2015. However, most books, websites, and developers, including myself, still refer to it by its informal name, ES6. There are many new features in ES6. We'll be covering most of them in this course. But a big important question is, can we use ES6 now in modern browsers? The answer is yes. The latest version of Chrome has 96% of new features implemented. The latest version of Firefox has 90% implemented, and Microsoft Edge comes in at 86%. We'll see later in this module how we can find out exactly which browser supports which ES6 feature. This is very important. So what new features do we have in ES6? Well, first of all, it's backwards compatible with ES5, so it's unlikely anything will break. New features include new syntax for arrow functions, lock-scoping, destructuring, and much more. We can organize our code into modules and classes now. Many existing JavaScript objects have been updated. Objects, string, number, math, all of the built-in objects are approved. We also have a new primitive type, a symbol. We'll see what symbols are, and how they're used. Iterators and generators are great new improvements on how we code in JavaScript. Also, we have a built-in promise object now, so there's no need for an external promise library. In addition to array being improved, we have new collection classes, including map, set, week-map, and week-set. The new reflect API and proxy API have let us perform meta-programming and work with objects and functions at a deeper level. The reflect API gives us a standard way of working with JavaScript objects and functions at a low level, and the proxy API let us wrap an object or function and control access to it. We can add security, profiling, logging, and other major features to our code by using these new reflect and proxy objects. As a prerequisite to this course, you'll need to be familiar with JavaScript. There are many beginner courses in JavaScript at Pluralsight. I assume you know the fundamentals of ES5. If you want a quick overview of JavaScript, or need to brush up on it, you can check out my prior course, "Rapid JavaScript Training". This course has a similar question-and-answer format as seen in my last course. Let's see an example slide. We're going to see if Number.parseFloat, a function, is identically equal to the global parseFloat function. We'll log out the answer with console.log. I'll ask, "What shows in the console?" And you'll have about two seconds to pause the video and take a guess at the answer. The answer will show up here; in this case, true. And then I'll go on to explain that ES6 is trying to do away with global functions. We should be working with number.parseFloat instead. Of course the global parseFloat is still there for backwards compatibility, but we should stay away from those global functions if possible. Because of the two-second delay between a question and answer, just be ready with the pause button, in case you want to work out the answer yourself or take a guess at it. So we're almost ready to go. In the next clip we'll take a look at a popular compatibility chart, which lets us know which ES6 features work on which browsers, or server-site technologies such as node.
An ES6 Compatibility Chart
As we work with ES6, we need to know which browsers are capable of which features, and there's a popular compatibility table that tells us all this information. I'm at Git Hub, and the url is kangax.github.io/compat-table/esx. And let's take a look at this table. First of all, on the left side, we have a list of all of the features of ESX. We see optimization and syntax for all the new syntax. We have bindings, functions, built-ins, and if there's anything new in ES6, it's listed here. So we won't look at all these features just yet. So the features are down on the left side, and if we scroll to the right, let's look at some of these headings here. Here's the compliers and polyfills and transpiler section. And if we look at these percentages, this is the percentage of ES6 features that have been implemented. For example, Babel and core-js implement 74% of ES6 features. Type-script and core-js implement 60%, and you can check out the values here. Scrolling more to the right, we have the desktop browser section. We can see Edge had 85%, and some of these go up a little bit, depending upon whether or not you have the developer flag turned on or not. But Edge actually goes up to 86%, the latest version of FireFox is at 90%, Chrome is at 96%, for the latest version, and so on. And of course one thing you can't forget, if you're targeting a wide range of browsers, is IE 11, which is only at 15%. So if you're going to target IE 11, you most likely need to use one of these compliers or transpilers, such as Babel or TypeScript. If we look further to the right, we have the servers and runtime section. Node 5.0 is at 56%, and there's even something I'm not familiar with, XS6, which is at 98%. And looking at mobile, you're going to need to use a transpiler on mobile, because Android 5.1 only implements 29%, and IOS 9 is only at 53%. So going back to the start of this chart again, a lot of these feature sections open and close. Syntax is open right now, and I can close it, and you'll notice here a 7/7. That means 7 out of 7 features are implemented. If I open it up, you can actually see the 7 features. If I mouse over this little icon here, we can see the test. The test actually runs in your browser. So the section is default function parameters, and it's checking for basic functionality. And you don't need to know this, but It is helpful in understanding ES6. This is the test that runs to determine whether or not the browser has the feature or not. So as you go through this course, feel free to consult this chart and see how kangax here at Git Hub actually checks for the feature being implemented. Now this chart does not cover modules. Modules have been separated out of the ES6 specification. And at this point in time, not many of these browsers actually implement modules yet. We'll talk about this in the course module titled, "Modules and Classes", and we'll see how to work with ES6 modules. But I'll go over in detail how to get up and running and be able to work with modules, even though they're not implemented in browsers yet. So now we're ready to go. As you go through this course, and you try out examples for yourself, be sure to consult this chart if something goes wrong. There's a good possibility one of the features isn't implemented yet in a certain browser. So definitely keep this chart open as you work.
New ES6 Syntax
Introduction
Hi, this is Mark Zamoyta and in this module we'll take a look at the new ES6 Syntax. The new Syntax makes JavaScript look a lot more modern. There are several fixes for things that went wrong in ES5 and earlier versions of JavaScript. Let's take a look at exactly what we'll be covering in this module. First we'll take a look at Let and Const, two new keywords to declare variables. We also have Block Scoping now in ES6. We'll take a look at Arrow Functions. That's the fat arrow function with the equal sign and greater than symbol. We no longer have to use the function keyword to declare functions. And we'll take a look at Default Function Parameters. We can specify a Default Value for a parameter that's not specified on a function call. We'll take a look at the Rest Operator and Spread Operator. Those are three dots and they're used for gathering up parameters to a function call that would be a rest parameter or it's used to spread out an object or an array or some other iterable and break it down into component parts. We'll take a look at Object Literal Extensions. Defining Object Literals has become simpler with a more modern syntax. We also have a For...of Loop now in ES6. For...of Loops are used to iterate through an iterable. Something like an object or an array. We have a new syntax for specifying Octal and Binary Literals. And we can now use Template Literals. A Template Literal is basically a way of using interpolation when specifying a string. And another popular feature of ES6 is Destructuring. We can now take something like an object or an array and destructure it which means break it apart into its component pieces and assign them to new variables or a new object. So, as you can see there's a lot more to ES6 now over ES5 and each one of these items will be covered in its own video.
let, const and Block Scoping
Let's take a look at Let, Const and Block Scoping. Let is a new keyword to use to declare variables and it does away with hoisting. Const stands for constant and we can now declare variables as constants, unable to change. We also have Block Scoping now. When we declare a variable within curly braces that becomes its scope. We no longer have to rely on Function Scope. So, let's see some examples. Here we're going to log out productID, but we declare productID after we log it out. What shows in the console? Undefined. This is an example of hoisting that we have in ES5 and also now in ES6. It stays the same. When we declare the variable productID , it gets hoisted and it gets set to undefined at the top of the function or in this case, we're at the Global Scope. Now notice that we're declaring productID with the Let keyword. What do you thinks going to show in the console? Well, we get a reference error: productID is not defined. So, no hoisting takes place when we use the Let keyword. That has a big potential for solving problems and doing away with certain types of bugs. Many developers in ES6 are now using Let and doing away with Var. Now we're declaring the variable before we use it. What shows in the console? Now we get 12. By using Let, we make sure that the variable declaration takes place before it gets used. In this case we're declaring productID with Let, but we're not assigning it an initial value. What shows in the console? Undefined. So, similar to the Var in ES5, if we don't initialize the variable it gets set to undefined. Now look at this example. We're declaring productID and we're initializing it to 12. Then, within a block within these curly braces here, we're going to declare it again and set it to 2000. What shows in the console? We get 12. So, now we have Block Scoping in JavaScript. We can redefine a variable within a block, right here and that variable will disappear at the end of the block. So, we get our initial value of 12 as the answer. Normally you wDouldn't have a block thrown in like this but you can assume that this would be part of an If statement or a Four Loop or some other syntax construct. How about this case? We have a block and we're declaring productID initializing it to 2000. What shows in the console? We get a Reference Error. productID is not defined. So, remember when the block terminates, our local variables are going to terminate as well. productID will not be defined. Now, how about this case: We're declaring a function and we're accessing productID, setting it to 12. But then we declare productID after we declare the function. We'll call the function and we'll log out productID. What shows in the console? We get 12. The use or productID here is said to be in the temporal dead zone. The compiler will run across it even though it's not declared yet. But it's not until later that we actually call this function so it does execute as expected Even though we declare productID after its use in the function. Here's another example with Block Scoping. We initialize productID to 42 but then we're going to re-declare productID as part of the Four Loop. We'll exit the Four Loop when productID is greater than or equal to 10. What shows in the console? We get 42. So the productID here is scoped to the Four Loop. When the Four Loop terminates, that's the end of that block and we'll go back to our initial variable declaration of productID which is 42. Here's an example of a problem in ES5. We'll create an array of update functions and then we'll have a Four Loop and for each loop iteration we'll add a new function to update functions. The function simply returns i. So, this loop will loop through the value zero and one for i. Then we're going to log out update functions element zero and we'll call that function. What shows in the console? We get 2. Now, this might be unexpected because for the first loop iteration, i is set to zero and you would think we would return zero. But this isn't the case. A closure gets formed over the variable i, and at the end of the loop, i is set to two so the function is always going to return 2. Remember we're using the Var keyword here. But what if we're using Let instead of Var? Now we're using the Let keyword to declare i. What shows in the console? Now we get zero. That's the value we expect. When we use Let in a Four Loop, each iteration of the loop will get its own i variable and any closures created will close over its own value of i. While this could hurt performance a little bit, it does away with that big problem of working with closures and now the closure works with the value that we expect. Just make sure to use the Let keyword instead of Var. Now we're declaring a variable with the Const keyword. What shows in the console? We get 100. Now, you notice that I'm using uppercase for the constant and that's standard in other languages. Whether that picks up in JavaScript in ES6 is unknown but it's a pretty common practice. Now we're declaring a constant but we're not initializing it. What shows in the console? We get a syntax error. When we use the Const keyword to declare a variable we must initialize it. So, now we're declaring markup percent, setting it to 100. Then we try to change markup percent to 10. What shows in the console? TypeError: Assignment to constant variable. Once we initialize a constant we can't change it. Now what about this case? We're declaring a constant and if that constant markup percent is greater than zero, which it is, we'll re-declare the constant and initialize it to 10. What shows in the console? We get 100. Remember we have Block Scoping now. When we work with Let and Const, anything declared in a block is only scoped to that block so by the end of this code, we're going to be working with our original constant market percent set to 100.
Arrow Functions
Another important syntax addition to ES6 is Arrow Functions. Arrow Functions use what's called the fat arrow symbol. The equal sign and the greater than sign, right here. Here we see an example of an Arrow Function. We'll declare getPrice and we'll set it to this strange looking construct here. So, here's our symbol for the Arrow Function. And before that, we have any input to the function. Because we have a set of empty parentheses, that means there is no input and we have to specify that. Then we have our fat arrow symbol. This is read in different ways according to who you talk to. Some people say, "Maps-to." Some people say, "Goes-to." But the way I look at it is, this input results in this output. And, 5.99 is the value that gets returned from the function. So, an Arrow Functions is a shorthand. We can leave off the function keyword and we can leave off the return statement so it might look a little cryptic at first, but the way I look at it is this input results in this output. This function will always return 5.99. So, let's log out the type of getPrice. What shows in the console? Function. So, we are declaring a function. Now, what if we execute this function? What shows in the console? 5.99 Again, in an Arrow Function we can leave off the return keyword. Whatever expression is specified there will get returned. Here's another example of an Arrow Function. Now our input is count. Notice that when we have one argument, we can leave off the parentheses. So, an input of count results in count times four being returned. And now we'll call getPrice with a parameter of two. What shows in the console? We get eight. So remember if there's no input to an Arrow Function we use empty parentheses, but if there's one input we don't need to use the parentheses at all. Now we have an Arrow Function with two parameters: Count and Tax. Now that we're using more than one parameter, we have to go back to using parentheses again. Let's call getPrice with values of two and .07, seven percent tax. What shows in the console? We get 8.56. That's just a more complex expression being returned, now. Two times four is eight and we multiply that by one plus the tax rate. Now, here's another example of an Arrow Function where we have two parameters for input we have our fat arrow symbol but now we're specifying a block and that's fine if we don't have a single expression that needs to returned, we can just go ahead and specify a block but in that case, we do need to specify the return keyword to return something from the function. What shows in the console? 8.56 So, this is exactly the same as the last example except we are using a block instead of a complex expression. So, Arrow Functions, they might save us a few keystrokes, but the real purpose of Arrow Functions is to handle the this keyword within functions. This has always been a confusing aspect of JavaScript. In ES6, Arrow Functions are an attempt to make that easier to understand. In this example, we're going to handle a click event on the document. The handler for click will simply log out (this). What shows in the console? We get #document which is our document. This might show differently in different browsers but in Chrome, it shows as #document. The important thing is that whenever we have an event handler in JavaScript in ES5, this gets set to the element that receives the event. This caused problems in ES5 because we wouldn't get access to the context of the function. We would only get passed in the element that receives the event. So now, look at this code. The only difference here is that now we're using an Arrow Function. We're going to handle click on the document and we're going to log out (this). What shows in the console? Now we get our Global Window object. We're in the global area here. We're not in the context of a function so this refers to the actual context of the code we're running which is Window, the global object. As long as we're using Arrow Functions, (this) is not going to be set to the element that's getting the event. It's going to be set to the context of the code we're in. Let's look at another example in ES5 of using (this). We'll create an Object Literal, setting number to 123 and process to a function. If we call that function we'll log out (this). And we do call it right here, invoice.process. What shows in the console? We get our object with number: 123. So, this ES5 code shows that (this) is being set to the object on which the function is called. In this case, invoice. But now we're using an Arrow Function. Here's the same example except we took out the function keyword and we're replacing it with an Arrow Function. What shows in the console? Again, we get the context of the code we're running. Our Global Window object. If we were inside a function we'd get the function's context. So, by using Arrow Functions, we're no longer going to get the object invoice returned here. We're going to get the context of the code we're running. Now, here's another example. We have invoice set to an Object Literal number 123. Process is a function but it's going to return a function and it's important to understand what's happening with (this) inside of the return function. So, we'll call invoice.process and we'll execute the return function. What shows in the console? We get 123 because process is a function here and that's context we're working with and because we're using an Arrow Function here returned by the process function, (this) will be set to that context and we will have access to number. Now, here's an important aspect of Arrow Function's. We have the same code from before. We're setting invoice to this same Object Literal but now we have a new object, new invoice. We're setting number to 456. We'll call invoice.process and we'll get our function but we want to bind a new object to the function. We'll bind new invoice which has number 456. What shows in the console? We get 123 and this is very important with Arrow Functions but you cannot bind a new object to an Arrow Function. The JavaScript engine didn't even throw an error. It just went ahead and ignored the bind. So, keep in mind that when we're using Arrow Functions, you're not going to be able to change the value of (this) with bind. Here we have a similar example except instead of calling bind, we'll call call and this applies to both call and apply. What shows in the console? Again we get 123. At first I would've expected an error, but it's just important to keep in mind that when we have our Arrow Function's, calls to bind, call and apply are all useless. We're not going to be able to change the value of (this). Now, here's another quirky example. We're setting up an Arrow Function getPrice. There's no input and it results in a return of 5.99. Let's look at the type of getPrice. What shows in the console? Well, we get a syntax error. Normally, when we're programming in JavaScript or other modern languages, adding a few extra new-line characters here and there is not a problem. But, when we're using Arrow Function's, it is a problem. We can't out the fat arrow symbol on a new line. Here's another quirk about Arrow Function's. We're setting up getPrice, that's straight forward. And let's log out getPrice.hasOwnProperty("prototype"). Normally, when we declare a function in JavaScript, it'll have a prototype property that's used in construction function's mostly. So, do you think getPrice, a function, declared with the fat arrow symbol has a property of prototype? What shows in the console? False. So, unlike ES5, in ES6 when we declare a function using the fat arrow, we do not have access to a prototype field.
Default Function Parameters
Let's take a look at Default Function Parameters in ES6. In ES5, if we didn't specify a parameter, its value would be set to undefined. But, now we have the ability to set a default. Let's see some examples. Here we're declaring a function and it has a parameter of productID. Notice that we have an equal sign and 1000. The function simply logs out productID. We'll call getProduct but we won't pass at anything. What shows in the console? 1000. So, this is how we specify a default in ES6. We used the equal sign and an expression. Here we're declaring a function and we have productID with a default of 1000. And we have another parameter type with a default of software. We'll log out productID plus comma and the type. Now, when we call getProduct, we'll specify undefined as the first parameter and the second parameter will be hardware. What shows in the console? We get 1000 comma hardware. So, because we're passing in undefined, JavaScript is going to go ahead and use the default of 1000 for the productID and because we specified type as hardware, it used that instead of the default. So, remember, if we specify undefined, JavaScript will try to use the default if there is one. Now, here our function accepts two parameters, price and tax. The default for tax is actually going to access price and multiply it by 0.07 and we'll log out price.tax. When we call getTotal we'll pass at five. What shows in the console? We get 5.35. So, when we declare a default, we are able to access the other parameters. You can think of it as having its own little scope right here in the function declaration. Here we're declaring variable outside of the function called baseTax. When we declare getTotal as a function, our default for tax is price multiplied by baseTax. So, do you think this is possible that we can access other variables when we set a default? What shows in the console? Again, we get 5.35. So, when we setup a default we do have access to variables that are in the context. What about accessing functions? Here we're declaring generateBaseTax. It's going to return a seven percent tax and we'll call that function within the default for tax. What shows in the console? Again we get 5.35. So, we can access a function when we're specifying a default. So, it's not a best practice to be using arguments within a function. But, still there's a lot of code out there that does access it. We're going to log out arguments.length and we'll call getTotal with a value of five. What do you think the length's going to be? We're only specifying one argument but the function takes two parameters, price and tax. What shows in the console? We get one. So, argument still refers to the number of arguments that are actually passed to the function. It completely ignores any default that gets set. Now, here we have a situation where we have two parameters, price and adjustment. But we're starting off by setting price to adjustment as the default. Adjustment does have a default of one. When we call getTotal, we're not going to pass at anything. So, the JavaScript engine is going to try to use defaults. What shows in the console? We get a syntax error. Use before declaration. Again you can think of this function declaration as having its own scope and in this case, JavaScript just doesn't know about adjustment yet. We know it'll get set to one as a default. But, JavaScript doesn't look ahead like that. Thus, we get the syntax error. Now, what about this case? We have our same getTotal function where we're setting the default for price equal to adjustment but now we're going to call getTotal with five as the price. Do you think we're going to get the syntax error again? Or do you think everything will be fine? What shows in the console? Everything's fine. We get the answer which is six. So, because JavaScript didn't need to look up the default, we didn't run into the syntax error. Let's take a look at creating a dynamic function with a default. We'll call new function to create a new function. All the arguments up to the last argument are parameters. We have a parameter of price and the default will be set to 20. And then the body of the function is right here. We'll return price. We'll log out a call to getTotal. What shows in the console? We get 20. So, these default parameter worked even when we're creating a dynamic function.
Rest and Spread
Let's take a look at Rest and Spread in ES6. Rest refers to gathering up parameters and putting them all into a single array. And spread refers to spreading out the elements of an array or even a string. Let's look at some examples. Let's declare a function showCategories and notice that we're passing it productID and ...categories. This ... symbol is the rest symbol. That will just gather up all the remaining parameters to the function and put it into categories. Now, within this function we'll log out categories, instance of, array. And we'll call showCategories with a lot more than just a productID. We'll also pass it search and advertising strengths. What shows in the console? We get true. So, categories will be set to an array. Now, here's a similar example except now we're going to log out categories. What shows in the console? We get search and advertising in an array. So, this rest parameter ... categories gathers up all the parameters that haven't been processed by the function. We know there's a productID, but everything else will get placed in categories. Here we're going to call showCategories passing it a single value for productID 123. We'll log out categories within the function. What shows in the console? We get an empty array. Even though there are no extra parameters to gather up, categories will still get set to an array which is empty. Here we're creating showCategories as a function. We want to log out showCategories.length which will tell us the number of parameters to the function. What shows in the console? We get one. When we access the length of a function, it's ignoring the rest parameter. It only sees the length as one because of productID. Here's a similar example except we're going to log out arguments.length. We'll call showCategories with three arguments: 123, search and advertising. What shows in the console? We get three. Again, even though it appears that we have two parameters, productID and categories, arguments is still going to refer back to the original function call where we're passing in three arguments. What happens if we create a new dynamic function? The first parameter will be a rest parameter for categories then the body of the function will return categories. Let's log out a call to showCategories where we pass search and advertising. What shows in the console? We get an array of search and advertising. So, it is valid to use rest parameters within one of these new dynamic functions. Now let's look at the spread operator. Spread is pretty much the opposite of rest. We'll declare an array of prices and then we'll call Math.max. If you're not familiar with Math.max you can pass it as many parameter as you want and it'll pick out the maximum value. Here's the Spread Operator and we're using it on prices and then we're going to log out the maxPrice. What shows in the console? We get 20. The Spread Operator took the prices array and divided it into three separate values, 12, 20 and 18. The maximum value of course, is 20. Essentially, it took an array and it converted it into a list of parameters to the function Math.max. Here again we're setting up an array of prices and we're going to use the spread parameter on those prices to create a new array. Do you think that's possible? We'll, log out the new array and see what happens. What shows in the console? We get the new array of 12, 20 and 18. We used the Spread Operator to spread out the prices but then we immediately created a new array for a new price array. What happens if we try to spread out empty values? Between the brackets here, we don't have any values but we do have commas so this would be a case where we're trying to load data that doesn't exist. There's some form of input file or some form of input that is missing values. We want to try to create an array from it. What shows in the console? We get undefined and undefined in a new array. Similar to ES5, when we create an array, we can assume that the values are undefined and also we can assume that there's nothing after the last comma. JavaScript is forgiving that way. We're allowed to put a trailing comma. In this case, this array only has two undefined values. We are able to use the Spread Operator on that array. Again, this is just another way to create an array. Instead of using new array or array, we're just going to use brackets. What shows in the console? Again, we get undefined comma undefined. Two undefined values in a new array. Now, what if we try to spread out a string? Here, we're calling Math.max. We have the Spread Operator on a string, 43210. What do you think's going to show in the console? We get four. The Spread Operator will break out a string into its individual characters and the maximum character here is four. In this example we're creating code array and we'll create a new array. The first element is A. Then we're going to spread out with the Spread Operator the string BCD. And finally, we'll add an element, E, to the array. What shows in the console? We get the elements ABCD and E.
Object Literal Extensions
Let's take a look at some of the extensions that ES6 adds for Object Literals. Here, we're setting two variables, price and quantity and then we're creating an Object Literal for productView. Notice that all we have listed are the values price and quantity. In the past, we might have seen price:price and quantity:quantity. Then we're going to log out productView. What do you think's going to show in the console? We get price: 5.99 and quantity: 30. This is a shorthand. We no longer have to specify the same string twice; as in price:price. We can just list it once. The Object Literals is smart enough now to know that we want a field called price set to a variable in the context called price as well and the same with quantity. This is just the shorthand that makes it easier to read and there's definitely less typing involved if we have a large number of fields. Here we're going to add a function to our Project Literal. The function is called calculateValue. Notice this new syntax. We're going to log out productView.calculateValue. What shows in the console? We get 59.90 and a little bit of a floating point error, there. We no longer need to specify the function keyword. We can use this short notation. We add the parentheses with any parameters and then the body of the function. But this example is a little deceiving. What does this.price refer to? And the same with this.quantity? Does it refer to the price in this object? Or does it refer to price in the context? Let's take a look at the next example. Here I'm resetting price to 7.99 and quantity to one. The rest of the example is the same. What shows in the console? Again, we get 59.90. So, when we use this function shorthand within an Object Literal, this is going to refer to the context of the code. So, it's as if we were using an arrow function with the fat arrow symbol. It's not referring back to the object that contains the function. If that were the case, the result would be 7.99. So, just keep in mind when you use this shorthand notation, it's as if you were using an arrow function. How about this example where we're attempting to use a string as a property name? We specify calculate value here, as a function. And we also try to access that on the object. What shows in the console? We get 59.90 again. So it is valid to have a space in a property name and we just need to wrap the name in double quotes. Because of that space we'll need to access it with the bracket notation off of the object. How about this example? We're setting field to a string dynamicField. We're setting price to 5.99. Then we create an Object Literal. But for the first property name, we're putting field in square brackets. What shows in the console? We get an object and it has one property. dynamicField:5.99. So, we can actually use a variable name and an entire expression as a field name. We just need to make sure that we use this bracket notation within the Project Literal. Here's an example where we're setting the property to field plus dash 001. What shows in the console? Now we have an object with dynamicField-001 set to 5.99. So, we can put an entire expression within the brackets. Do you think we can do the same with a method? We're setting a variable method to do it and then we're setting an Object Literal, productView equal to this. Now we saw that this worked with properties. Does it work with methods as well? What shows in the console? In a method. So, this works fine as well. In a Project Literal, not only can we use the new function notation, but we can use these brackets as well to create a dynamicField name or property name. Here we have an Object Literal with a getter and a setter. Again we are using bracket notation with the variable ident. Ident is being set to productID. What happens when we log out productView.productID? What shows in the console? True. So, these dynamic property names work with getters and setters as well.
for ... of Loops
We have a new kind of For Loop in ES6. It's the For...of Loop. For...of works with iterables. We'll cover that later in this course but we can still see some examples now and learn a little bit about iterables. In this example we're setting up a categories array to hardware, software and vaproware. And then we have our For...of loop. For var item of categories, we're going to log out item. Now, categories is an array which is an iterable. We're able to iterate through all the elements of it easily. So, what do you think's going to show in the console? Hardware, Software, Vaporware. Essentially, this For...of loop looped through each element of categories. How about this example? Categories is set to an array but for whatever reason the data didn't show up. All we have are two commas. What shows in the console? Undefined and undefined. So, categories was set to an array of two undefined values. Remember there's not a third because JavaScript lets arrays end with a comma. The first undefined is before the first comma and the second undefined is considered between the two commas. We are able to loop through those values with a For...of statement. How about this example? We're setting codes equal to ABCDF and we're initializing count to zero. We're going to use a For...of loop to loop through all of the codes. What shows in the console? We get five. So, not only can we iterate through the elements of an array, but we can iterate through the characters of a string. Again, we'll learn more about iterables later in the course. For now you can use this For...of statement with strings and arrays and anything else that JavaScript happens to expose as an iterable.
Octal and Binary Literals
ES6 has new support for Octal and Binary Literals. Octals were confusing in ES5. If you were in strict mode, Octals wouldn't even work. If you weren't I strict mode, a lot of people didn't even realize that pre-pending a number with a zero would create an Octal value. So, this is all straightened out now in ES6. Let's look at examples. Here we're setting value to zero and then a lower case o, one zero. What shows in the console? We get eight. This is how we specify an Octal value. Octal values begin with the number zero and are followed by the letter o. In this case, it's the lowercase o. Because we're working with Octals, the value one zero is equal to eight, our answer. What if we specify an uppercase o now instead of a lowercase o? What shows in the console? We get the same value eight. So, it's case insensitive but this does look pretty confusing having a zero followed by an uppercase o so I would use the lowercase o, but you don't have to. For Binary Values take a look at this. Zero b one zero. What shows in the console? We get a two. So, just like Octals, we're replacing the o with a b, and we get a Binary Value. One zero in Binary is two in decimal. What if we use an uppercase b? What shows in the console? Two. So, again, case doesn't matter.
Template Literals
A cool new feature of ES6 is Template Literals. Now, by template we simply mean a string template with interpolated variables and expressions. Let's see some examples. Here we're setting invoiceNum to 1350. Here's a Template Literals right here. Notice that it's surrounded by the back tic character. That's usually found under the escape button on a keyboard. What shows in the console? We get invoice number 1350. So, interpolation takes place when we run into these symbols: dollar sign followed by curly braces and in this case, the variable invoiceNum gets interpolated. It gets placed into the string directly. Here's the same exact example except the syntax highlighting is different. I just wanted to create this slide just to show that depending upon your editor, sometimes the interpolated value is shown with different highlighting. In this case, we have the orange invoice number colon and it ends with an orange back tic. However, the interpolation characters are all white. What shows in the console? Invoice number 1350. Same as the last example. How about this example? Notice that the dollar sign is escaped with a backslash. What shows in the console? Invoice number: dollar and then we have our invoiceNum in curly braces. So, by escaping the dollar sign, no interpolation takes place. Here's another interesting characteristic of Temperate Literals. We're spreading out our string, ABC over three different lines. What shows in the console? We get ABC on different lines. When we create a Template Literal, white space such as new lines and tabs are maintained. How about this example? We have our template string, but we're not just interpolating a variable. We have an expression here. INV- is a string plus invoiceNum. What shows in the console? Invoice Number: INV-1350. So, we are allowed to have expressions within the curly braces. Now here's an interesting example that shows when this interpolation takes place. We're setting invoiceNum equal to 1350 and we're going to interpolate invoiceNum within that message. But now we look at the body of showMessage and you can see that we're setting invoiceNum equal to 99. We're going to log out the message. So, is our message going to show 1350 or is it going to show 99? What shows in the console? Invoice Number: 1350. The interpolation takes place first, before the function call. Here we have an example of what's called a tagged Template Literal. It looks a lot like a function call without the parentheses. It is going to call a function called processInvoice. We'll pass this Template Literal which is simply the word template, we won't work with any interpolation just yet, and we'll be setting the parameter segments and then we'll log it out. What do you think's going to shows in the console? We get an array. The array has one value, template. That's the value of our Template Literal. Let's look at a more complex example here. We're setting invoiceNum to 1350, and we're setting amount to 2000. Now we have a tagged Template Literal. The tag is processInvoice so that's the function that's going to be called. We have a much more complex Template Literal than in the last example. We show Invoice: and then the invoice number is interpolated, for is a string, and then amount is again interpolated. If we look at the function processInvoice we can see two parameters: segments, which we saw in the last example as an array, and rest values. We have the rest symbol so we know values will be an array and it will get populated with everything following segments. We'll log out segments and values. What shows in the console? Well, let's look at our segments first. Segments end up being the unique strings within the Template Literal. Invoice: space is the first string. The second string is space for space. And then to end it off we have an empty string. So, those are our tech segments within the template. Then, values holds our interpolated values. InvoiceNum is 1350, and amount is 2000. This is how we can create a function to handle a template for ourselves. We don't have to rely on JavaScript's default handling. We can create one of these tagged Template Literals. In this case, the tag is called processInvoice.
Destructuring
Next, we'll take a look at Destructuring in ES6. Destructuring means to take apart the structure of something. Usually it's something like an array, where we take out the individual elements or an object where we can destructure or take out individual fields and reassign them. We can also destructure a string into characters. Let's look at examples. Here we have an array salary with three values. Next, it looks like we're creating an array with this Let statement but we're not. We're actually destructuring salary. What shows in the console? 50,000. So, what's happening here, is we took salary, the array, and we took its first three elements and assigned them to new variables: low, average and high. Low became 32,000. Average became 50,000. And High became 75,000. We just log that average. So, this is an example of destructuring an array. Taking apart the elements and assigning them to variables. How about this example? Now, salary only contains two values: 32,000 and 50,000. We're going to destructure salary into low, average and high. We're going to log out high. What shows in the console? Undefined. There obviously wasn't a third element in salary so, high became undefined. How about this example? We have three values in salary again but we have white space between these two commas here: low comma comma high. What is high going to be set to? We'll log it out. What shows in the console? 75,000. This is how we skip elements. We can just specify an extra comma with white space between the commas. How about this example? Salary is set to an array of three values and in our destructuring statement, we're specifying low, comma and a rest parameter remaining. We're going to log out remaining. What shows in the console? 50,000 and 75,000 within an array. So, low was set to 32,000 and all of the other elements in salary went to the remaining array. Not only can we use rest parameters within functions as we saw earlier in the course, we can also use them in destructuring. Again, remaining is an array. Here's another example of destructuring with a default. Notice that we're specifying high equals 88,000. What shows in the console? 88,000. Salary contains only two elements which get set to low and average so the default is picked up for high. Here we have nested arrays. Salary is set to 32,000, 50,000, and the third element is an array of 88,000 and 99,000. We have an array within an array. When we're nesting like this, can we destructure as well? We're going to try to destructure salary into low, average, actualLow and actualHigh. We'll log out actualLow. What shows in the console? 88,000. We are able to pull out nested values. You would think actualLow and actualHigh become an array but they don't. These are being destructured from a nested array. So far we've been destructuring within variable declarations but the variables can already be declared. In this example we're declaring low, average, and high. When we destructure, we're actually doing it as an assignment, now. What shows in the console? 88,000. So, again, destructuring can take place in declarations or in simple assignments like this. Let's take a look at this example. We're going to call reviewSalary with an array: 32,000 and 50,000. If we look at reviewSalary we can see that we're attempting to destructure that array into low and average parameters. Do you think this is possible? We're going to log out average. What shows in the console? 50,000. So, the destructuring did work. Low became 32,000 and average became 50,000. We can destructure arrays going from a function call into setting the parameters of a function. Now, let's take a look at destructuring objects. We have an Object Literal, salary. And we have low, average, and high values. Now, notice when we were working with arrays, we were using square brackets to destructure, but now, we're working with an object and we're using curly braces. What shows in the console? 75,000. This is how we destructure objects. We use curly braces otherwise it's the exact same as destructuring an array. We just have to make sure that properties match up between the object and our new variables. Here's an example where we have the same Object Literal, but we have new syntax when we destructure. We're specifying low:newLow, average: newAverage, and high: newHigh. We're going to log out newHigh. What shows in the console? 75,000. So, in the destructuring here, we can see that the value on the left of the colon, in this example low, is the property in the original object and we want to assign a new variable: newLow. And the same with average and high. We're not forced into using the properties of the object that we're destructuring. We can rename the properties. Or, it's more accurate to say, "We can take the property "and give it a new variable name." So, in this case, newHigh becomes the original object.high which is 75,000. And again, when we destructure these objects, we're not forced into doing it by initializing variables. We can do normal assignments. Here we have an example where we're not initializing the values in the destructuring. We're going to try to destructure this on this line right here. We already declared the variables with a Let statement. What shows in the console? We get a Syntax Error. Everything looks fine. If we look closely, especially at these curly braces here, we can understand that the JavaScript compiler is looking for a block of code. Curly braces start and end code blocks so this just looks like a syntax error. How do we fix this? Here you can see that we added parentheses around the entire destructuring statement. What shows in the console? 75,000. So, we didn't have this problem with arrays, but when we destructure an object you need to remember those parentheses if we're working with assignments. If we're working with a Let statement, and destructuring as we initialize variables, we don't need the parentheses. It's only when we have assignments like this. Here we're going to attempt to destructure a string: AZ. We're going to try to set maxCode and minCode. What shows in the console? Min: Z and Max: A. So, we can destructure a string and it destructures into its individual characters, in this case, A and Z.
Advanced Destructuring
We just saw the most common scenarios for using destructuring but there are some edge cases and some nuanced uses of destructuring. Let's take a look. Here, we're going to attempt to destructure this array which has a single element, undefined. Do you think this is valid in JavaScript? What shows in the console? High: undefined, low: undefined. So, this is not an error. Our data didn't show up, but at least we do have an array and undefined values are used for both high and low. Here we're going to try to destructure undefined. Do you think that's valid in JavaScript? What shows in the console? We get a Runtime error: Unable to get property 'Symbol.iterator' of undefined or null reference. So, destructuring requires an iterator which we'll see later in this course. Just know for now that it is a Runtime error if we try to destructure undefined. How about trying to destructure null? What shows in the console? We get the same exact Runtime Error. If you noticed from the error in the previous slide, it says, "Of undefined or null." They're both in the same boat, here. It's illegal syntax. You'll get an error thrown. Let's take a look at exactly what error gets thrown when we try to destructure undefined. We have a Try Catch block and we'll log out the name of the error that gets caught. What shows in the console? TypeError. Here we have a case where we have a trailing comma. This could easily happen in a case where we dynamically create some code. Do you think this is still valid syntax? What shows in the console? High: 500, low: 200. So, as always, JavaScript allows for that trailing comma. Here we have an example of attempting to destructure an array in a For...of statement. Our array has one value which is another array containing five and 10. We'll attempt to destructure into the variables A and B. What shows in the console? Five and 10. This form of destructuring is allowed within a For...of statement. Here's a similar example but we want to get a count of now many times this loop actually executes. What shows in the console? Well, we get five and 10, as the logged out values for A and B and it only executes once. There's only one element in the initial array that happens to be a secondary array.
Summary
Let's take a look at the new syntax features we covered in this module. First we looked at the new Let and Const keywords for declaring variables. These work with Block Scoping. We looked at Arrow Functions which is a modern and concise way of declaring a function. This also solves the big problem of using the (this) keyword within a function. Now, (this) refers to the context in which it appears. In ES6 we can set defaults for parameters, now. We looked at the Rest and Spread Operators. The ... as a Rest Operator gathers up parameters into an array and as the Spread Operator it'll take an array or an object and spread it out into arguments. We looked at Object Literal Extensions. There are now cleaner ways to specify properties, functions, and we can even use bracket notation to setup a property by using an expression. We have For...of Loops now where we can loop through an array, an object, a string or any iterable. We have new Octal and Binary Literals. Now we can use zero and the letter o to specify Octal Values and zero and the letter b to specify Binary Values. We have Template Literals, which have to do with interpolation. By using the back tic character instead of quotes, we can easily embed the value of variables within a string. Finally, we looked at Destructuring. We can destructure an array or an object and assign its elements or properties to new or existing variables.
ES6 Modules and Classes
Introduction and Setup
Welcome to this module titled ES6 Modules and Classes. ES6 gives us a much better way to organize our code now. You've probably already been using modules in JavaScript, but now the syntax has been standardized. So we'll look at that syntax in this course module, and we also have classes now in ES6. Classes take us a step closer to the classes we find in C# and Java, but they can still be considered wrappers around the ES5 constructor functions. So we'll take a look at that in detail. One problem that we do have with modules in modern browsers is that the browsers just don't implement this new syntax yet. Module loading has been separated out into its own project. So in this introduction we'll go over this course module, but we'll also see how we can get set up so that we can load modules properly in browsers. We'll start off by looking at Module Basics. This will cover basic importing and exporting and working with different files. Next we'll look at Named Exports. It's an interesting concept but when you export a function, are you actually exporting the code of that function or do you just export the name of the function? Well we'll see how in ES6 modules we're exporting the name, so if the underlying code for a function changes within that module, we can still access that new code with the old name. Next we'll look at Classes, which can be viewed as a wrapper around constructor functions from ES5, but we'll see the new syntax, and there are new keywords for classes, extends and super. Extends will let us use inheritance to set up the prototype and super is interesting because it can be used to access the prototype chain whether you're working with a class or just with an object literal. Next we'll look at Constructor Function Properties. There's no new specific syntax to handle properties in classes, so it helps to think of that class as a constructor function. And we'll see how to work with properties similar to how we did in ES5. And we'll take a look at Static Members. A class can have static members at its class level instead of per instance, and finally we'll look at new.target. New.target is a strange symbol because new is a keyword to create a new instance of an object and now there's a property.target on that keyword, and that's just one of the things that makes ES6 unique. This is just a concept that doesn't exist in ES5. New.target gets sent to the initial constructor function that gets called, and we'll see examples of that and why we would use it. So since we're going to start off by working with modules, let's see how we can get our environment set up so that we can load modules properly. First of all, here's a simple HTML page that I use for testing. It's got the bare minimum for a webpage and you can see here that we need two scripts. The first script is a transpiler. Here I'm using Tracer, but you could also use Babble or something else. And most importantly we need the ES6 Moduler Loader, and we'll see where to get these packages in just a second. But once we have this transpiler and Module Loader all we need to do is call system.import and pass it the base module. In this case the name is base.js, and let's look at that for a second. I'm simply logging out of Message. So as we'll see in this course module this is where we will import other modules and execute some code. Now when we do create new modules, there's no need to place them in our HTML. All the module loading is handled by this system object. So our HTML file doesn't need to change as we add new JavaScript files. We can finally stop listing everything as a script here. So let's take a look at where we can get Tracer and the ES6 Module Loader. I'm here at GitHub and we're at google/tracer-compiler, and this is Google's project for Tracer. You can load this with the download zip button or you can get it directly into GitHub desktop or Visual Studio or you can load it with NPM, and the Module Loader requires this; this needs to be listed before we add the script tag for the Module Loader. Let's look at that one. The Module Loader is located here, ModuleLoader/es6-module-loader on GitHub. Likewise you can download the zip or load it some other way. So once you have these two packages, the only important files we need are tracer.mend.js and the es6-module-loader-div.js. And for the purposes of this course, everything will be kicked off by the base.js module.
Module Basics
So let's take a look at some modules in ES6. We should be all set up. If you watched the introduction, we saw how we're using the ES6 Module Loader to load a module called base.js and that'll be the basis for all of the examples in this module. So let's take a look at some example code. So here's our file base.js. We're just going to log out in base.js and this is the module that's being directly loaded by the ES Module Loader. What shows in the console? in base.js. So simply by loading the module, it gets executed and a module only gets executed once on its initial load. Here we're setting projectId to 99. Notice that we're not entering strict mode and we're also not declaring this variable properly with a var, const, or let keyword. What shows in the console? We get a Runtime Error, variable undefined in strict mode. So simply by loading a module, the ES6 Module Loader will put it in strict mode and that's part of the ES6 specification. If this weren't in strict mode, it would go ahead and create projectId in the Global Namespace. So now we're adding a second module. We'll call it module1.js and a typical ES6 application will have dozens, maybe even hundreds of these modules, but for now let's see how these two base.js and module1.js work together. So in base.js we're going to import projectId from module1.js and we'll log out that projectId, and if we're going to import in base.js, we can see that in module1, we have an export command. We're going to declare a projectId and set it to 99 and then we'll export projectId. What shows in the console? We get 99. So these import and export commands are the basic way to communicate between modules and share information. So here we're going to import two values, projectId and projectName from module1 and we'll log out projectName has Id with the Id. In module1.js we'll export two different values, one for projectId and one for projectName. What shows in the console? BuildIt has id: 99. So we can actually export as many values or functions as we wish. We're not just limited to one. Here's a similar example, except notice the as keyword. We're going to import projectId as id, and then we'll use Id when we log out our string. What shows in the console? BuildIt has id: 99, same as before. So using the as keyword, we can assign an alias to an imported variable. So when do we use an alias such as id right here? What happens to the original variable name that's exported projectId? What happens when we try to log that out? What shows in the console? We get a Runtime error, projectId is undefined. So once we set an alias, we need to use that alias. So let's take a look at the sequence in which these modules get loaded. We know that our ES6 Module Loader is going to load base.js first. So in base.js, we're going to log out starting in base. Then we're going to call import and then we're going to log out ending in base. And in module1.js, we'll export projectId and we'll log out in module1. So we want to take a look at the order in which these modules load. What shows in the console? in module1 starting in base ending in base. So it's interesting, but an import statement actually gets hoisted. The Module Loader will scan for the import statement and hoist that to the top of the file. So that's why the first thing that prints out is in module1. Module1.js does execute first. Once that's complete, it goes ahead and executes base.js and we get starting in base and ending in base. So the important thing to remember is that import statements get hoisted and dependencies will execute first. So here in base.js we're going to import some value, but notice the curly braces are gone. In all the previous examples of importing we had curly braces, then would log out some value. In module1 we'll export projectId and we'll declare projectName and set that, but notice now we're going to export default projectName. We added the default keyword. What shows in the console? BuildIt. So by leaving off the curly braces in the import, the Module Loader is going to look for the default export and a module can have one default. Here it's projectName so that's what prints out. We can also explicitly show that we want the default and we're giving the default an alias, myProjectName. What shows in the console? BuildIt. So it is possible to give the default export an alias name this way. We just need to use the curly braces again. So here we're going to import some value again and looking at module1.js, we're going to export both projectId and projectName, but we're not specifying a default. What shows in the console? Undefined. Because there's no default, it's ambiguous what should be imported. So the Module Loader just sets some value to undefined. And here's a similar example but now in module1 we're going to export projectId as default, and we're also going to export projectName. What shows in the console? 99. So this is valid syntax to export a default. As we saw before, we can export default variableName or in this case we can export projectId as default. Either way is fine. And here we're calling import * as values. * is the wildcard character and it refers to all the exports. If we look at module1, we're exporting projectId and projectName. What shows in the console? projectId 99, projectName BuildIt, all as an object. So when we import everything with the * character, we need to give it an alias, and that alias will become an object whose properties are all the exports from module1 in this case. So we would access all the exports as values.projectId and values.projectName. And when we use the wildcard that alias is required.
Named Exports in Modules
When we export values and functions from a module the exports are usually referred to named exports and there's certain characteristics we have to keep in mind when we're working with these named exports. Let's take a look at some examples. So here we're going to import projectId and then we're going to try to assign projectId the value 8,000. Then we'll log out projectId. In module1 we're simply going to export projectId as 99. What shows in the console? We get a Runtime error, projectId is read-only. So the named exported projectId can be accessed as a variable in base, but we can only read it. We can't try to change its value. So how about this example? We'll import project and quickly looking at module1 we're exporting project as an object that has one property, projectId set to 99. We'll attempt to change that project.projectId. We'll set that to 8,000 and we'll log that out. What shows in the console? 8,000. So while project is read-only, as an object we're free to modify its properties. So here's another important example. It's a little bit more complex. We'll import project and showProject. Looking at module1, project is the same object as before, and showProject is just going to log out project.projectId from module1. So back to base.js, we import project and showProject and we set project.projectId to 8,000. Then we call showProject. Do you think that value 8,000 carried over into module1 as well? And then the final line of base.js we'll log out project.projectId. What shows in the console? 8,000 and 8,000. So when we grab an object out of a module and modify it in a separate module, the two modules stay in sync. We are able to edit the contents of that other module by using properties of an object. We've mostly been working with properties and objects but it's going to be very common to also export functions and those work in a very similar way. Let's take a look at this example. We'll import showProject and updateFunction. Those are both exported functions in module1.js. ShowProject is simply going to log out in original and updateFunction is going to create a new function and set it to showProject. So by calling update function, the original function for showProject is going to get wiped out and replaced with this new function, which logs out in updated. Going back to base.js, we'll import showProject and updateFunction, then we'll call showProject, we'll call updateFunction, and then we'll call showProject again. Do you think showProject is going to hold on to that initial function? Or do you think that by calling updateFunction, showProject in base.js will get reset to this new function? What shows in the console? in original and then in updated. So that's what we mean by named exports. When we export showProject, we're only exporting the name showProject as a function. We're not exporting the actual function. So when that name changes, showProject, if it gets updated to a new function as we saw here, base.js will call that new function. So the named export showProject is just the name and as that function changes in module1, base.js will be able to call that updated function.
Class Fundamentals
Let's take a look at classes in ES6. There is a new class keyword and classes do take a step more towards what you would expect out of C# or Java. But still there's a lot missing and one way to look at a class in ES6 is just a new syntax to work with prototypes and the constructor functions that we would use in ES5. Let's see examples. So here we're defining a class called Task. The body of the class is created between curly braces and here we can see an empty body. Let's log out typeof Task. What do you think's going to show in the console? Function. So there is no new class type per say, so you can think of a class as being somewhat of a constructor function as used in ES5. But we'll see more of that and this in the next video. Here we have our Task class and we instantiate it. Let task = new Task, and that's also similar to how we would new up a constructor function or an object in ES5. So let's log out typeof task. What shows in the console? Object. So now we have an instantiated task. Here's a similar example, except we'll log out task instanceof Task. What shows in the console? True. So we are able to determine the class of an instantiated object using instanceof. Here I added a method to Task, showId. You can see that this uses the same shorthand notation used for functions in an object literal. There's no need to use the function keyword. So we'll new up Task and then we'll call task.showId. What shows in the console? 99. So that's the most basic form of calling a method on a class. So where does showId actually exist? We added it as a method to Task, but we know Task is actually a function. This example is similar to the last one except let's log out task.showId is identically equal to Task.prototype.showId. What shows in the console? We get true. So adding a method to a class is similar to adding that method to the prototype object. So this is a good example of how a constructor function and adding methods to the prototype in ES5 is still done in ES6, we just have a new class syntax for it. We can also have constructors in a class. We can just specify constructor and this takes no arguments and we'll just log out constructing Task. What shows in the console? Constructing Task. So just by creating a new instance of Task, the constructor will be called. And just to make sure we have the syntax right, if we were working with object literals, we would separate each property with a comma. What if we added a comma here between the constructor and a method showId? What shows in the console? We get a syntax error. So make sure to leave those commas out when we're working with classes, and we do need them in object literals. What happens if we try to declare a property inside of our class? Here we'll declare a variable, taskId, and initialize it to 9,000. Do you think that's valid syntax in a class? What shows in the console? No, you get a syntax error. We'll see later how we can work with properties and static properties within a class. But for now, the class body is not a place to declare variables. So you're probably familiar with hoisting in ES5. So let's take a look at this example. We're going to create a new instance of Task before we actually declare Task as a class. What shows in the console? Error: Use before declaration. So classes are not hoisted. Another interesting thing about classes in ES6 is that we can use them within an expression. Here we're going to create a new class, Task, and we're going to assign that to newClass. So what happens when we new up newClass? We'll execute it with th open and closed parentheses. What shows in the console? Constructing Task. So we can assign classes to variables and use them in expressions in a similar way we could work with functions and constructor functions in ES5. So in this example we're creating a constructor function called Task; we're going to put the classes aside for a second here. Then we'll create a new object called Task. It won't have any properties, and then we'll execute the call function on Task, passing it our new object. And this perfectly legitimate code in ES5. What shows in the console? constructing Task. We are able to call Task.call and pass it a new object, which will become this within the function body. But now, what if we do the same thing with a class? In the previous example we used a function, but now we're using a class. We'll make the same call to Task.call passing it Task, the this object. What shows in the console? Error: class constructor cannot be called with the new keyword. So this is one important difference between a constructor function and the new class in ES6. We can't call the call function in order to change the this object. And here's another interesting thing about classes. Let's create a constructor function called Project that'll be empty and we'll log out window.Project. That's the Globalspace window, and we'll compare that and see if it's identically equal to Project. What shows in the console? True. So when we create a new constructor function without any kind of namespace attached to it, it gets placed in the global area. But does the same thing happen when we're working with classes? We'll define a new class, Task, and we'll log out window.Task is identically equal to Task. What shows in the console? False. So by creating a class, we're not polluting the global namespace; it doesn't get placed on the window object or some other global object if you're working in Node.
extends and super
Let's take a look at how we can use inheritance with classes in ES6. The inheritance mechanism is still the same as ES5. It uses prototypes, and if you want to learn more about that you can check out my course, Rapid JavaScript Training. That course was the prequel to this one. And that'll go into detail on how prototypes work, but we can use the extends keyword on a class to set the prototype in ES6. Also within a constructor or within a method of a class we can call super to explicitly access a function on the prototype. Let's see how this works. So here we have a class Project with a constructor. It simply logs out constructing Project. We have a second class called softwareProject and you can see that it extends project. Then we'll create a new softwareProject. What shows in the console? constructing Project. So even though softwareProject itself has no constructor and no methods, it does extend project. Project does have a constructor and in ES6, when we're working with classes that constructor will be called. Here's a similar example except now we're going to pass in an argument to the constructor. We'll new up softwareProject passing it Mazatlan, and the constructor will accept the name argument. What shows in the console? constructing Project: Mazatlan. So then again even though the softwareProject doesn't have a constructor of its own, it still calls projects constructor passing arguments. Here we have Project again with its constructor, but now softwareProject itself has a constructor. We're still extending Project, and the constructor calls super. Then it logs out constructing softwareProject. Then we'll create a new softwareProject instance. What shows in the console? constructing Project, constructing softwareProject. So by calling super in the constructor of softwareProject, the JavaScript engine will know when to instantiate Project and call its constructor. So Projects constructor will log out the string first because of the super call. So now what happens if we comment out the call to super? What shows in the console? We get a ReferenceError, this is not defined, and that's interesting because we're not using the this keyword and this problem is sure to cause a lot of confusion once programmers start working with classes. This really looks like valid code, but we're getting a ReferenceError. And the rule of thumb is that if softwareProject is going to have constructor, it needs to call super. That's when JavaScript knows when to instantiate a new prototype object. So by having a constructor in softwareProject and not calling super, we're going to get an error. If you don't want to call super, then just leave the constructor out. So now we still have super commented out in softwareProject, but let's also comment out the constructor in Project. So there's no constructor in Project and we do a constructor in softwareProject. What shows in the console? We still get a ReferenceError, this is not defined. Even though Project doesn't have a constructor, we still need to instantiate it as the prototype, and it's the JavaScript's engine to do that and it would do it in the constructor of softwareProject. So we still need that call to super. So we've already seen this slide, but I just wanted to show it again because this is the pattern and this is the proper way to be working with inheritance and constructors. Project has a constructor and we'll extend it with softwareProject. And softwareProject's constructor needs to call super. Without it, you're going to get an error. What shows in the console? constructing Project and constructing softwareProject. So we've been looking at constructors, let's take a look at methods. We have a class Project with a method getTaskCount which returns 50, and then we have a class softwareProject which extends Project and the class body is empty. We'll new up softwareProject and we'll log out p.getTaskCount. What shows in the console? 50. So even though softwareProject doesn't have the method getTaskCount, it does extend Project, which becomes the prototype. That does have a getTaskCount, so 50 is returned. So here we have Project still returning 50 for getTaskCount, but now in softwareProject, that has its own method called getTaskCount, which returns 66. We'll new up a softwareProject and we'll log out p.getTaskCount. What shows in the console? We get 66. So even though we're overriding the prototype, we didn't need to specify any kind of override syntax. We could just go ahead and create getTaskCount and it'll automatically override Projects getTaskCount. That's why 66 gets returned. Now we can also use super within a method, not just in a constructor. So Project is the same, but in our getTaskCount for softwareProject we're going to return super.getTaskCount + 6. What shows in the console? 56. So by calling super.getTaskCount the JavaScript engine is going to look up the prototype chain for a getTaskCount, and it finds it in Project. So that'll return 50 and we tack on six to it to get 56. So we've been using extends and super with classes, but let's take a look at using super with object literals. Here we're creating an object, Project, with a method getTaskCount returning 50, and again, we're not using a class; it's just an object. Next, we'll create an object literal for softwareProject, and that has a method getTaskCount and it returns super.getTaskCount + 7. Now we want to link these two objects together. We want to set the prototype of softwareProject to be Project. So that's easy enough in ES6. We can do it with this line right here, Object.setPrototypeOf. softwareProject will have the new prototype project. We'll log out softwareProject.getTaskCount. What shows in the console? 57. So using super is perfectly valid in an object literal. You just need to make sure your prototype gets set up right.
Properties for Class Instances
Let's take a look at using properties on classes. We can have properties at the class level and we'll cover that in the next video. Here we're just going to look at properties for class instances and for class instances in ES5 we would use the this keyword often, and that hasn't changed for ES6. But what has changed is now we're using classes with constructor as a function, and that's where we can initialize the this object. Let's take a look at some examples. Here we have class Project and in the constructor we're setting this.location equal to Mazatlan, and this constructor function spelled out as constructor is very similar to the ES5 version of a constructor function something that you would new up. And this is where we would initialize instanced variables with the this keyword. We're also going to extend Project with the class softwareProject and the constructor will simply call super. We'll create a new softwareProject, assign it to p and then we'll log out p.location. What shows in the console? Mazatlan. So there really isn't much difference here for instance based properties. We just initialized things in the constructor and we always use the this keyword as in ES5. Here's a similar example except now we're using the let keyword instead of this. The rest of the code is the same. What do you think's going to show in the console? undefined. So by using let, const, or var in this location it goes out of scope very quickly. It won't be attached to an instance. And of course we can access this across constructors. We're back to using this.location and the main difference in softwareProject now is we'll set this.location equal to this.location + Beach. So we're accessing this.location across constructors. What's going to show in the console? Mazatlan Beach, and the key as we saw earlier is to always call super before we access this.
Static Members
Let's look at static members in a class. Here we have class Project and we have a static method, getDefaultId. We do have a static keyword in ES6 and we'll just return zero for this method. Let's log out Project and notice we're not instantiating Project, we're calling Project directly here. We log out Project.getDefaultId. What shows in the console? Zero. So by declaring a static method, the method gets attached directly to Project as a constructor function. And this function is accessed as we see here by using the classname Project.getDefaultId. So here we have the same class with static getDefaultId, but now let's new up Project. We create a new Project called p and we log out p.getDefaultId. What shows in the console? Error: Object doesn't support property or method getDefaultId; so just remember when we're creating static methods, they're attached to Project, the class, or another way to think of it is the constructor function. So do you think we can do the same thing with a property? We'll create static, let id = 0 and we'll log out Project.id. What shows in the console? We get a Syntax Error. At this point we can only have static methods, but we can still create properties the same way we would do in ES5. Here we have a class Project and we're creating Project.id setting it to 99. We'll log out Project.id. What shows in the console? 99. So we can still create static properties this way by attaching it directly to the class or constructor function.
new.target
ES6 has a strange new property called new.target. Now we know new; new is a keyword to instantiate a class, and that keyword somehow has a property now called target and that might seem a little bit strange, but it's a very important concept to grasp when we're working with class constructors. Let's take a look at how it works. So new.target is mainly used in a constructor. Here we have a simple class and we're going to log out the typeof new.target. We'll instantiate Project. What shows in the console? function. So new.target is a function, but what function? Let's take a look further. Now that we know it's a function let's just log it out. We'll log out new.target and instantiate a Project. What do you think's going to show in the console? Well we get the constructor console.log(new.target). So new.target is pointing to the constructor directly and that's not very useful right here, but it starts to become useful when you start working with inheritance and extending other classes. Let's see another example. So here we have class Project and in the constructor we'll log out new.target. Now we also have sofftwareProject extending Project and that constructor we'll simply call super. We're going to instantiate softwareProject and assign it to p. What shows in the console? We get constructor and I called a super in the constructor. And if you notice, new.target is being set to the constructor that was initially called. We're not creating a new Project directly. We're creating a new softwareProject, and the only reason Projects constructor is getting called is so that it can be a prototype for softwareProject. So that's the definition of new.target. It's also going to point to the initial constructor that got called. So what about this case? Again we have class Project set up. It's going to log out new.target in the constructor, and we have softwareProject extending Project but we're not specifying a constructor. You saw earlier that the constructor in Project will be called, and we'll new up a softwareProject. What do you think's going to show in the console? Well we get a constructor and it's collecting up args using the rest operator and then it's passing those arguments along to super with a spread operator. And this is the default constructor that JavaScript creates for us. You can imagine that this constructor actually exists within softwareProject directly. So when we log out new.target from Project we're getting the default constructor that belongs in softwareProject. It's created automatically for us. So what's the use of new.target? Why do we need this? Well it's useful if you just need to refer back to the original class that's getting created. Here we can see that we're accessing new.target.getDefaultId, and if we look at softwareProject it has a static method, getDefaultId returning 99, so we can get access to static methods on the initial constructor call by using new.target, even though it's part of the prototype chain. What's going to show in the console? 99. So our call to new.target.getDefaultId works fine, but only because getDefaultId is static.
Summary
In this course module we looked at Module Basics for ES6. We saw the fundamentals of how to import and export modules. Next we looked at Named Exports. We saw how we can export an object or a function, but we're not necessarily exporting that object or function directly. We're exporting it's name, so we can access it as it changes in the module. We looked at Classes and we saw how they worked in a similar way to ES5 constructor functions. We saw the new syntax for it now. And we looked at extends and super keywords. We use extends to set up their prototype chain and have inheritance, and we can use super within a class or within an object literal itself to directly access something on the prototype chain. And we looked at Constructor Function Properties. There's no new syntax for properties on classes so this just works in a similar way to ES5 constructor functions. And we looked at Static Members. By using the static keyword we can directly add a function on the class itself. So a static function wouldn't exist on an instance itself, just on the class. Finally we looked at new.target. new.target will tell us which constructor function was actually called initially. You would access it from somewhere on the prototype chain, such as an extended class. So essentially what it does is it tells us what the developer used the new keyword on, and we can access static members on that class.
New Types and Object Extensions
Introduction
In this module we'll look at New Types and Object Extensions in ES6. The main new type is the symbol type, and just about every major object in ES5 has been extended some way. First of we'll look at symbols. Symbols is a brand new concept in ES6, and a symbol is used mainly to provide some kind of unique string, and it's guaranteed to be unique and in fact as a developer you'll never even know what that unique ID is. The main way that symbols are used in ES6 as of right now are to add properties to classes. For instance what if you wanted to add a property to every class in your system? Maybe it had to do with security or maybe it had to do with logging or possibly debugging. How can you guarantee that whatever property you add to all these classes will be unique? Well, that's what we use symbols for. You can also use a symbol to provide some kind of unique string as an ID, but we'll see how to work with this new type. There are also well-known symbols. These are symbols that are used as properties on objects that are standardized. For instance when we work with iterators later in this course we'll see how to access an iterator of an object by using it's well known symbol. Now Object with a capital O has been extended, and we'll take a look at all of the new functions added onto that, and there are String Extensions for the string class, there are Number Extensions which clean up some of the problems we've had in ES5 working with numbers. There are Math Extensions adding lots of new mathematical functions, and Regular Expressions have been extended, and a big part of that is now working with unicode in a better way. And finally we'll look at Function Extensions. Mainly there's a name property that's been standardized and we can work with that now, so let's get started with this module and take a look at symbols.
Symbols
Let's take a look at symbols in ES6. This is a brand new concept that doesn't really exist at all in ES5. The purpose of a symbol is to generate a unique identifier but the trick is we never get access to that identifier as developers. There's just no way to inspect it to see what the identifier is, we just know it's going to be unique. Here we have the symbol constructor function or class now that we're in ES6, and we're passing it a string resize event. This string is mainly used for debugging purposes. What's going to happen is a new symbol is going to be created and that new symbol is going to have a unique ID within the Java Script dungeon, and we'll assign that unique symbol to eventSymbol and then we'll log out typeof eventSymbol. What do you think the type is of one of these symbols? What shows in the console? Symbol, so that's a new type in ES6. Now we want to create a new symbol again, it's human readable debug identifier is resize event and we'll assign that to eventSymbol. Now let's log out eventSymbol.toString. What do you think is going to show in the console? We get Symbol(resize event) so that's just letting us know what the initial string was when we created the symbol. It still doesn't tell us anything about what the unique ID is, and there's just no way to access it, and that's a good thing. That way we can guarantee we have a unique ID. One way we might want to use symbols is to create a constant, and we can be guaranteed that this constant will have a unique value, so here we're creating a symbol with the debug string calculate event, and assigning it to all uppercase calculate event symbol as a constant. What shows in the console? Symbol(calculate event) so we can assign a symbol to a constant or something that we declare normally with let or var. Let's take a look at this example. We'll have s equals Symbol(event) and s2 will also equal Symbol(event). Normally symbol creates a unique ID, but if we pass it the same string twice, do you think it's going to create the same ID and it won't be unique? We'll compare s with s2. What shows in the console? False. So every time we call symbol, even if it's a string we've used before, we'll get a unique symbol, and we can get around that by using Symbol.for which we'll see next. There is a built in symbol registry and we access that registry by calling Symbol.for, passing it a string, so if we already have a registered symbol that we specified as event, that will be returned. Otherwise a new symbol will be generated, so we'll assign that symbol whether it exists or a new one is generated to s. What shows in the console? Symbol(event). Let's create a symbol s Symbol.for event. We know that that's going to generate a new symbol for us then the second call to Symbol.for will also pass an event so symbol is going to look up the string event in the symbol registry and return it to s2, so the question is, is s identically equal to s2? What shows in the console? True, even though we have no idea what the IDs are for those symbols we can still compare them, and we can test for equality. And just to check and make sure that a registry is being used, let's call Symbol.for(event) and assign that to s and we'll call Symbol.for(event2) and assign that to s2. Is s identically equal to s2? What shows in the console? False, so we do have two totally different symbols here. Let's create a variable s and we'll assign it Symbol.for(event), so that will be a brand new symbol. Now let's say that symbol gets passed around to several functions and gets held as a property on certain objects and at some point in the future, we want to see what that symbol represents. Here we're calling Symbol.keyFor(s) so we're passing in our symbol and assigning that result to description. What do think is going to show in the console? Event, so once again we always get the human readable debug string that was initially used to create the symbol. We never get the unique ID. So now that you that a symbol represents a unique ID that we'll never get access to, you're probably wondering what good is it? Like what do we use this for? As we saw we could compare one symbol to another to see if they're equal or not, and that could be valuable in a few circumstances, but the majority of the cases where I've used symbols or I've seen them used is as a property, whether in a class or an object. Let's take a look at this object literal article. We're setting title to Whiteface Mountain, and we're setting this interesting property to My Article. As we saw earlier in the course, we can use an expression to create a property on an object. We just need to put the expression within brackets. Here the expression is Symbol.for(article) so in this case that unique symbol will be used to create a property. Again we have no idea what that symbol is, but we can create a property out of it so now below block says article, the object, and we use bracket notation to look for the property symbol for article, and we'll assign that properties value to value, and we'll log out value. What do you think is going to show in the console? My Article. It's interesting that we can use a property on an object without even knowing what the property is. We're just using a symbol and this gets used a lot especially when we're working with iterators and generators which we'll see later in this course. Here we have this same article set up again with our special symbol property being set up but now we're going to log out something different. Let's log out Object.getOwnPropertyNames passing an article. This is going to return a list of all of the properties attached to article. Do you think we're going to be able to see what this symbol finally is? What shows in the console? All we get is the title, it completely ignored the symbol. We can't get access to the symbol this way by calling get own property names. Let's look at this example. Here we're calling Object.getOwnPropertySymbols passing an article, and this is a new static method on Object for ES6 which will show the properties which are symbols. What shows in the console? Symbol(article), so once again we don't get access to the actual symbol, we just get access to the string that was used to create it.
Well-known Symbols
There are symbols that are used for metaprogramming. Metaprogramming involves looking more deeply into objects and functions and even how the JavaScript engine operates, and there are several symbols that we can use to access some unique features of ES6. These symbols are called well-known symbols and let's take a look at some. One well-known symbol has to do with printing out an object as a string. Here we have a simple constructor function Blog, we create a new instance of it and we call blog.toString. What shows in the console? We get (object Object) which probably isn't too useful to us. Let's see how we can actually change this message in the JavaScript engine by using a well-known symbol. Here we have our Blog constructor function, and on Blog's prototype we're going to access a field called Symbol.toStringTag. toStringTag is the well-known symbol, and it's placed directly on this symbol function or class, so when we set this property on Blog's prototype, we'll set it to Blog class. It will instantiate a new Blog, and we'll call blog.toString. What do think is going to show in the console? Now we get object Blog Class, which is a lot more useful. This is an example of metaprogramming. We're altering The way the JavaScript engine calls toString and produces output. There's another well-known symbol that has to do with concatenation. Let's look at this example first. We have a simple array of three values, and we'll create a new array and we'll concat(values). What shows in the console? We get eight, 12 and 16, that's expected. We just concatenate the three elements into the new array. But now we have a well-known symbol called isConcatSpreadable and by setting that to false, that will prevent us from spreading out an array with concat so we'll log out a new array .concat(values). What shows in the console? Now we get an array but it only has one element which is the original array, so here we're getting an array within an array and the elements are not spread out into their own elements. How about this example? We have an array of values, and we'll let sum equal values plus 100, and we'll log out sum. What shows in the console? We get a really strange string. It's going to show values as a string which is 8,12,16, but then it tacks on 100 as a string to that. What if we want to get control over the value of this values array? There's a well know symbol called toPrimitive, and again all these well-known symbols are placed directly on the symbol class. We can set values and the property of Symbol.toPrimitive to a function. The function accepts a hint as to how to convert this into a primitive value, and we'll just log out the hint, and we'll return 42, so essentially the value of this array will be 42. This is just an example. Then we'll let sum equal values plus 100, and we'll log out sum. What shows in the console? Default and 142. We won't go into detail on what the hint is, we just want to convert it into it's default value and we'll see in a second how to find out more information about these well-known symbols on MDN, but the 142 comes about because when the JavaScript engine evaluates values, it will call our function, and 42 will be returned, so we get 42 plus 100 as the answer. There are several of these well-known symbols. Probably the most well known of the well-known is used with iterators, and we'll look at that in a lot of detail in the module on iterators, but for now let's just take a quick look at the MDN website. I'm here at developer.mozilla.org and I searched for symbol, and here we can see all of the documentation on symbol. If we scroll down we have listed out the well-known symbols and this is a good reference to find out what these well-known symbols are and how they can be used, you just need to be careful because not all browsers support all of these symbols yet.
Object Extensions
Several new methods have been added to Object, let's take a look at them. Here we have an object literal a, and another object literal b. There's a new static method on Object called setPrototypeOf and we already saw this in this course, but let's look at it officially here. setPrototypeOf will take the a object and assign it's prototype to be b, and we'll log out a.y. What shows in the console? Two, so even though the property y doesn't exist on a because we set up the prototype, we can select sa.y through the prototype chain. Let's look at another function called Object.assign. We'll create two object literals, a and b. We'll create an empty object called target, and we'll call Object.assign passing it target a and b, and we'll log out target. What do you think is going to show in the console? We get target and it's set to an object a with the value of one and b with the value of two, so Object.assign will populate a target with all of the other parameters, so it's going to populate target with the properties of a and b. Here we have a similar example, except notice that a has a property a set to one, and b also has a property a set to five, and the rest of the code is the same as the last example. We're going to call Object.assign, we're going to populate target with a and b. What shows in the console? Target is an object where a is five and b is two, so we can see that the parameter b, its properties overwrote the properties of a, specifically b.a overwrote a.a. Here's another example. We set up our object literals, but now we wanted to define a property on b. We'll call the property c and we'll give it a value of 10, but we're defining it this way because we want the innumerable to be false and then we'll call Object.assign passing it target a and b. What do you think is going to happen to b.c? Is that property going to be included or not? What shows in the console? Our object contains the properties a five and b two, so c does not get included because it's not innumerable. Properties are innumerable by default, but if you're creating your own properties and you want them to work with Object.assign, you got to make sure innumerable is true. How about this example? We're creating object literals for a, b and c, and we're going to call Object.setPrototypeOf passing it b and c, so b's new prototype is going to be c. Then we'll call Object.assign again in the same way. We'll populate target and we'll pass it a and b. The question is, do you think the prototype chain is going to be walked, and do you think that properties from the prototype will get assigned to target as well? What shows in the console? Well, we only get the properties a and b, so when we call Object.assign, it's going to look directly on the objects for properties, it's not going to walk the prototype chain at all. Here's a piece of code from ES5 that some people just don't agree with the functionality of it. We'll set amount to not a number, and we'll log out amount is identically equal to amount. What shows in the console? False, because we're working with not a number, this expression is false, and you would think that amount would always be identically equal to amount. Now in ES6 we have a new function on Object called is, and that's very similar to the identically equal to symbol, the three equals signs, but let's see if it fixes this problem. We'll set amount to not a number, and we'll log out Object.is amount and amount. What shows in the console? True, so that's one of the two things that Object.is fixes. People would expect that a variable would always be identically equal to istelf, so a better way to compare now is with Object.is. Here's another problem that was common in earlier versions of JavaScript. We're setting amount to zero and we're setting total to negative zero. Personally I don't use negative zero too much and I try to avoid it as much as possible, but this is a problem in some situations for some people, but the question is is amount identically equal to total? What shows in the console? True, and that's the way it's been in JavaScript by definition. Zero is identically equal to negative zero, but now we have Object.is. Let's compare amount to total using Object.is. What shows in the console? The answer is false, so if you want these fixes, or if you don't consider them fixes, if you want this new functionality, you can just use Object.is instead of the triple equals sign for identically equal to. And lastly we already saw this example, but there is a new static function on object called getOwnPropertySymbols, and this will just pull out the symbol properties and give you an array of those, so what shows in the console? We get Symbol(article).
String Extensions
Let's look at extensions to the string object in ES6. Here we're setting title to Santa Barbara Surf Riders, and we'll call a new function called startsWith on title. What do you think is going to show on the console? We get true, so that's pretty straightforward. startsWith lets us know if a string starts with a given string. And how about calling title.endsWith? What shows in the console? False, the string actually ends with Riders with an s, but endsWith does exactly the opposite of startsWith, it looks at the end of the string. There's also a function called includes that we can call on strings, so we'll call title.includes and the string ba. What shows in the console? True, it just looks to see if that string, ba, exists anywhere in the title string. Here we have a new unicode syntax for ES6. We can escape the u character and within curly braces we can put the hex value of any code point, even astral plane values. If you're not familiar with astral plane values, those are values that take more than for hexadecimal digits to display. For example here we have five hexadecimal digits, so that's an astral plane value. In ES5 it would take two different escape sequences to create this value, it was very confusing. What do you think is going to show in the console? We get surfer's blog, and we get the emoji symbol of a surfer there. That's what unicode code point 1f3c4 is, and these astral plane values mainly have to deal with things like the emoji characters, gotten really popular now, and things like advance mathematical symbols and other technical symbols. Here we're setting the variable surfer to that same unicode character, and we want to log out surfer.length. What shows in the console? Two, so it's important to point out here that even though we have a single emoji character specified by 1f3c4, the JavaScript engine still chops it down into it's two other symbols, and surfer.length is not really an accurate value, but let's see how we can work with that. Here we're setting surfer to three emoji characters. We're escaping u and specifying the codePoint for each one of the three. Next we got a console.log(Array.from(surfer).length) so we're going to call Array.from passing it the string and we'll get the length of that new array and lastly we'll just log out surfer so we can see what these emoji characters are. What shows in the console? Well we get a length of three for the array, so even though the string .length was wrong, we can still work with that string of unicode characters by creating an array out of it, calling Array.from(surfer) and we can see the characters below. There's a wave and a surfer and a whale about to eat the sufer. Here's another common problem in ES5. We'll create a title variable, and set it to Mazatla. The \u0301 that represents a tilde character that gets placed on the a before it, so if we count the characters M-A-Z-A-T-L-A with a tilde n, there's only eight characters there. The unicode codePoint 0301 gets placed on the character before it, so let's log out title, space and title.length. Do you think it's going to show eight or nine? What shows in the console? We get Mazatlan properly with the accent, but it chose nine characters when there's only eight real characters showing. Most likely we would want the answer to be a, we would want the a with the tilde to be transposed into it's own character. And now we see a fix we have for this in ES6. We'll set up title the same way, but this time we're going to log out title.normalize. Normalize is a new function we can call on a string instance and then we'll print out the length of the normalize string. What shows in the console? Mazatlan eight, so in order to properly handle these types of characters, and get the length right, we can call normalize on the string. Here we can see that we're going to log out title.normalize again but we want to get the codePoint at index seven, so we want to see what character is actually there where we expect the a with the tilde above it, and we'll call toString(16) That will print it out as a hexadecimal string, b16. What shows in the console? We get the ascii value 6e, and that's the a with the tilde. You can see that by calling normalize, it converted our unicode codePoint 030a with the a proceeding it into a simple ascii value which is also unicode 6e. We can also call String.fromCodePoint to set a string from a hex value. What shows in the console? We get our surfer, that's the emoji hex value for the surfer. Let's take a look at this example. We're setting title to Surfer but then we're setting output to String.raw and that's a template tag. The template is set to $(title) which will be surfer, a space and then the unicode character for a surfer along with a new line character. String.raw is a new function but mainly it's used here as a template tag. What do you think is going to show on the console? We get the raw string. It did perform the interpolation on $(title) but it did not create the emoji character. It kept our \u codepoint for the surfer character, that stays the same, and it also kept our \n, our newline character the same, and that's what String.raw is all about. It does the interpolation but it doesn't process any of the other characters on the string. Here we're setting wave to a unicode character, again it's an emoji character in an astral plane and we're going to call wave.repeat passing at a 10. What shows in the console? We get 10 waves so the repeat function on an instance of a string simply repeats the string a number of times. Wave is just one wave but we repeat it 10 times.
Number Extensions
Let's look at some extension to the Number Object in ES6. We've been using parseInt in ES5, but now parseInt has also been placed on the number object as a static function. Do you think Number.parseInt is identically equal to parseInt? What shows in the console? True, so you're probably better off using Number.parseInt even though they're the same function. parseInt without the number is a global function and the movement seems to be towards getting rid of global functions or at least replacing them with something more reasonable like Number.parseInt. And the same with parseFloat. Is Number.parseFloat identically equal to parseFloat? What shows in the console? True, so just like with parseInt you're better off using Number.parseFloat. Here we're setting s to the string value not a number, or NaN, and notice that it's a string value. It's not a real NaN value. We'll log out isNaN passing at s and that's the ES5 way, and we'll also log out Number.isNaN, that's a new function on number. What shows in the console? We get true and false, so the ES5 global function converts the string into a real NaN and returning true, but the new Number.isNaN returns false because it's a string. Let's set s equal to a string 8000, so again it's not a number, it's a string. We'll log out isFinite passing at s and we'll also log out the new Number.isFinite passing at s. What shows in the console? We get true and false, so again the global function is finite, converts it into a number and it is finite, true is returned. But for the new function Number.isFinite, we get false, because it's a string not a number. Here we're setting sum to 408.2. There's a new function on number called Number.isInteger. What shows in the console? We get false. It's got a decimal to it so it's actually a floating point number. Let's see what we get when we pass a whole bunch of different values to Number.isInteger. We'll pass not a number, infinity, undefined and three. What shows in the console? isInterger not a number returns false, isInterger(Infinity) returns false, undefined also returns false, and just to make sure that this actually works, three does return true, so all those special cases will return false for isInteger odd number. There's also a new function called Number.isSafeInteger and a safe integer is an integer that can be accurately represented using floating point notation. Floating point values lose their position after a certain amount of time, and happens to be two to the 53rd power. That's the highest and the lowest integer that can be shown so here we're setting a to Math.pow, we're raising two to the 53rd power and subtracting one and we'll log out Number.isSafeInteger(a) then we'll reset a to Math.pow, we'll raise two to the 53rd power, and we won't subtract one this time. And we'll log out Number.isSafeInteger(a). What shows in the console? We get true and then false. The highest positive safe integer that we can show is two to the 53rd minus one, and there are three new constants that are added to number. Number.EPSILON, Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER. What shows in the console? If you get this right I'm sure you're some kind of calculus doctorate or something. You get three really super big or small numbers. It's just important to know that these constants do exist now if you need them.
Math Extensions
Let's look at some extensions to the Math Object. First of all we now have Hyperbolic functions, we have cosine, sine, tangent, all the arc values of those, so if you're interested in Trigonometry, you can go ahead and look it up on Mozilla Developer Network. There are also a few more Arithmetic functions. We can calculate the cube root with cbrt, we can calculate in zeros and binary digits, we can calculate exponent and x-1, log base two, log base 10, log(x+1) and we can multiply 32 bit integers and we'll be looking at a few of these soon that might be useful. There are also some miscellaneous functions, some of which are really useful. There's sign which gives us the sign of a number, it will be one, negative one, zero, negative zero or not a number, and we'll see some examples of that. There's truncate where we can truncate the decimal part of a number and leave the integer there, and there's fround, round to the nearest 32 bit floating point value. I'm not going to go into all of these but let's take a look at some that could be important. Here let's look at the sign of various numbers. We'll look at the sign of zero, negative zero, negative 20, 20 and not a number. What shows in the console? We get zero, the sign of zero is zero, that makes sense. The sign of negative zero is negative zero, that also makes sense unless you're using the edge browser in which case the answer is zero, and I don't know if that's something that's going to be fixed soon or what, but it is a problem I ran into. The sign of negative 20 is negative one, and the sign of 20 is one. The sign of not a number is not a number, so hopefully that makes things easier and our if comparisons will look a little simpler. Let's log our Math.cbrt(27). What shows in the console? Three, three times three times three is 27. Three is the cube root. Let's log out Math.trunc(27.1) and we'll also log out Math.trunc(-27.9) What shows in the console? Our 27.1, it truncates the 0.1, and for -27.9 just to make sure we're not rounding it down to -28 it does truncate the 0.9, and we're left with -27.
RegExp Extensions
Let's look at Regular Expressions and the extensions that were added for ES6. Here we're setting pattern equal to a regular expression, and we'll specify a new astral plane emoji character. We'll escape u and in curly braces we'll put the symbol for our surfer, 1f3c4. We'll log out pattern.test, so we'll be testing for the surfer character. What shows in the console? We get false. Everything looks right like it should work, but for backward compatibility reasons, there's a new flag that we need to use if we're going to be working with this astral plane unicode characters. And here we can see the flag, it's a u for unicode. This is the same example as the last one, except for this u. What shows in the console? Now we get true, so if you need to test for astral plane characters, make sure you use the u flag in your pattern. Here's another problem with using unicode characters with regular expressions. We're setting pattern equal to, the carat represents the beginning of the line and that represents any one single character, so carat.surfer, any one character surfer. We'll call pattern.test and we'll pass it our surfer character and the word surfer. It looks like this should pass the test. What shows in the console? False, in ES5 these astral plane characters, such as emoji characters get chopped up into two separate characters, so this doesn't match. The way we fix it is the same we saw in the previous slide, so that the regular expressions can handle unicode characters properly. What shows in the console now? We get true. The surfing character is seen as a single character now. There's a new flag which is the y flag, we can see it here. The y flag performs the search from the last index, and the last index only. A pattern is set to 900 with the y flag. We'll log out pattern.lastIndex, and when we start working and testing a regular expression lastIndex will be set to zero, so I'll give you that one, but the next one will log out pattern.test(800900). What shows in the console? We get zero like I said, and then false. The string 900 definitely matches our test string, but because of the y flag, we're only looking at lastIndex which is zero, so index zero is actually 800, that's why we get a false. Let's look at this example. Our pattern is the same 900, and we're specifying the y flag, but now we're manually setting the lastIndex to three. We'll log out pattern.test with a test string of 800900. What shows in the console? True, because the 900 in our test string does start at index three, that's why we get true. And there's a new property on regular expressions. It's called flags and simply enough, it just tells us what flags were set for that regular expression. What shows in the console? We actually get gy not yg like you would expect. The order is important and pattern.flags will always return items in the order gimuy, so even though we specified the flags as y and then g it doesn't matter, the Flags property sets it to gy.
Function Extensions
The Function class does have one important extension. It's actually a name property put on the function now which can be useful in some scenarios, especially logging. Here we have a function expression, we're setting the variable fn to the function calc, then we want to log out fn.name. What shows in the console? We get calc and that's what we expected. In a lot of cases we actually don't give the function a name right there, we don't need to. It's a good idea but what if we left the name off? Here we're setting fn to just a function which is anonymous. We'll log out fn.name. What shows in the console? Well we get fn. fn.name takes on the value of the variable. The function is anonymous so the JavaScript engine does the next best thing and uses the variable it's assigned to as it's name, and this has been common behavior in most browsers but now it's required behavior in ES6. It's a standard now. How about this example? We'll set fn to an anonymous function, then we'll set newfn to the original fn. We'll log out newfn.name. What do you think is going to show in the console? fn, so even though we assigned fn to a new variable, the function name remains the same. And how about classes? Let's look at this calculator class, it has a constructor and an add method. We'll create a new instance of Calculator, and we'll log out Calculator.name, and we'll also log out the instance c.add which is the method.name which shows in the console. Firstly we get calculator, so the name of the class is actually the class name itself which makes sense, and the name of the method also gets set properly. So function.name works exactly how we would expect it to now, and it is standardized. The one thing to remember about function.name is that it isn't writeable. We can't change it directly. However we can configure it with Object.defineProperty. I went over Object.defineProperty and defineProperty is the plural form of it it in detail in my Rapid JavaScript training course, so if for some reason you do need to modify function.name you can watch my prior course or just look up Object.defineProperty at MDN or some other reference site.
Summary
In this module we looked at Symbols. Symbols is a new type in ES6, and we saw how it guarantees a unique ID or string. We looked at well-known symbols. These are used for metaprogramming, where we can work with iterables or in in this example here we can let the concat function know whether it's spreadable or not using the spread operator. We looked at Object extensions. There are several functions added to Object which help us work with prototypes and other features, and we looked at String extensions. A lot of those have to do with unicode. We looked at Number extensions and we saw how problems were solved by using isNaN and moving that over to Number.isNaN for is not a number and several other functions were added as well. We looked at Math extensions and here we can see that sign function that was added to Math and there are several others, and we looked at regular expressions extensions. The big thing that was added was support for unicode. We can now work with astral plane characters such as emoji characters and other advanced character sets. And finally we looked at the name property on functions. This has been in use for a while in ES5 but now it's been standardized in ES6.
Iterators, Generators, and Promises
Introduction
Hi, my name is Mark Zamoyta and welcome to this module titled Iterators, Generators and Promises. Iterators and generators are brand new to JavaScript, and promises have been around for a while, but use them mostly in jQuery or the QLibrary. But now in ES6 we have native support for promises. We'll start off this module by looking at iterators. An iterator is an object that lets us iterate through an array, an object, a string, or even one of our custom objects. And then we'll take a look at generators. A generator is a special kind of function. The function can yield right in the middle of execution and return to the calling function. So if we wanted to create a function that would return a database set row by row, we could create a generator for that. And we'll take a look at the new syntax for this. Next we'll take a deeper look into yielding within a generator. So when a generator yields, and again, a generator is just a function, it can yield and give a value back to the calling function. Likewise, when that generator is called multiple times, we can pass information back into the generator. So you can think of it as a very long running function that constantly passes information back and forth. We'll also take a look at throw and return. These are functions on iterators that work well with generators. What happens if something goes horribly wrong in our code and we need to throw an exception in the generator? We can do that with the throw function. Likewise, if we want the generator to terminate, we would call return on its iterator and that would return the final value and shut the generator and iterator for it. Next we'll look at promises. These are the native, built-in promises that we have in ES6 now. And essentially a promise is an object that's waiting for some kind of asynchronous resolution. Usually it's some kind of network access or a timer. And finally, we'll look into promises in more detail. The final video of this module will cover some static methods that are added to promise, and these static methods usually work with multiple promises. For example Promise.all will wait for all of the promises passed to it to resolve, and Promise.race is only interested in the first promise to finish, even though we're passing it multiple promises. It will only act on the first one completed. So these three objects, iterators, generators, and promises work hand in hand. An iterator is used by a generator in order to process information one row at a time or one piece of asynchronous information at a time. And a generator, if it was performing some kind of data access would most likely be using a promise. So let's take a close look at these three objects, and we'll start off with iterators.
Iterators
Let's take a look at iterators in ES6. Iterators are a major new feature and they're found all over the place. Let's take a look at what they are and how they work. Here we have a simple array, ids, and arrays now have a special property. We have to access that property with bracket notation because the property name is actually a symbol. We can access it by using Symbol.iterator. And this is one of the major functions of symbols is to add properties to classes while guaranteeing that the property name is unique. And Symbol.iterator will be unique. It's a symbol. So we're going to log out typeof ids What shows in the console? Function. So the special property is a function. Let's try calling it here. We'll access the property again and we'll call it. We'll assign the result to it, which will be our iterator. Once we we have the iterator, the main function called on iterators is next. What shows in the console? Well, we get a special object. it.next kicked off our iterator, and the object we got has two properties, done and value. Done is set to false, meaning we haven't exhausted the iterator yet, and value is set to 9000, our first id. So here's another example, except let's call iter.next a few times. We'll call it twice here and then we'll log out the third time. What shows in the console? Our object contains done: false and value: 9002. So the first two iter.next calls resulted in 9000 and 9001, but notice that for the third call, we get the final value, 9002, but done is still set to false. The iterator hasn't been exhausted yet. So here we'll call iter.next three times, and we'll call it one last time, logging that value out. What shows in the console? Our object contains done set to true now and value is undefined. So when the iterator is exhausted, this is the object we get. So iterators are built into arrays. JavaScript puts them there for us. But we can also make our own iterators. We just have to follow the examples that we just saw for arrays. Let's create an object literal called idMaker. We need it to have the same property we used on arrays, so we'll use bracket notation and the field name will be Symbol.iterator. We'll add a local variable, nextId and initialize that to 8000. And then we'll return an object which will be used as the iterator itself. it needs a function called next, we saw that we need to call next on the iterator, and will return value and done. Value will be set to nextId, which will get incremented, and done will be set to false. So there's no complex logic in here to set done to true at some point. For now, this iterator will just go on forever. So let's look at consuming this iterator. We'll call idMaker and we'll access these special fields, Symbol.iterator, and we'll call it as a function. So now we have our iterator. We'll log out it.next, and let's just look at the value, and we'll do that twice. So we're not going to look at the whole object being returned, just the value field. What shows in the console? 8000 and 8001. So we can see it's very simple to create an iterator. We just need Symbol.iterator, a well-known symbol, and we need that to exist as a field, which will return our iterator. We've already looked at for-of loops in this course, and for-of loops work off of iterators. So let's take the idMaker we made in the last slide and let's use that in a for-of loop. We'll create a variable, v, for value, of idMaker. So as we loop, we'll grab the next iterator value. If that value is greater than 8002, we'll break out of the for-of loop and we'll log out the value. Remember, our iterator will iterate forever, so we need this logic here, at least for now, to break out of the loop. What shows in the console? We get 8000, 8001, and 8002. So for-of works are meant to work with iterators. Usually the logic in an iterator will be a little bit more complex. So I just added a bit of logic here in idMaker where it will check the nextId, and if it's greater than 8002, will set value to undefined. Otherwise, we'll get the nextId. And we'll set done = !value. So if value is undefined, done will be true. We'll be done with the iteration. So now our for loop doesn't need any logic in it. We can exhaust the iterator. For each loop, we'll log out v, the value. And if you're wondering, I just had to compress this to save space on the slide. What shows in the console? 8000, 8001, 8002. So, normally the iterator will determine when it's done, not the for-of loop. Now we've seen this spread operator before, and the spread operator works off of an iterator. Here ids is an array, but ids could be any object that's iterable, it has that iterator interface to it. So ids will be iterated over and the parameters to process will be assigned each value. What shows in the console? 8002. 'Cause we're only logging out id3.
Generators
Next let's take a look at generators in ES6. A generator is a special kind of function. It doesn't run all the way through, necessarily. It's able to yield and be called multiple times throughout all of your code. It doesn't exist on the stack the way most functions do. And we actually use an iterator to call a generator multiple times. Let's see how this works. Here we have function *process. However, the asterisk here shows that this function is actually a generator. Now the asterisk could be attached to function, or it could be between function and process, surrounded by spaces, but this is how many other programmers and myself like to code it. Now you'll notice that this function is a normal function. We could put normal code in here, but we happen to have new statements, which are yield statements. We're going to yield a value, 8000, and then we're going to yield another value, 8001. And then the function exits. The interesting thing about a generator is that when we run it, the result is actually an iterator. It starts off at a pause state, so when we run it, we get an iterator and we assign it here to it, a local variable. Then we'll log out it.next. What do you think is going to show in the console? We get an object with done set to false and value set to 8000. So essentially what happened is our function process paused at the yield and it returned 8000 as the value. It never even got to yield 8001. Here we have the same generator, except we'll call it.next by itself, and we'll call it.next again. What shows in the console? Our object has done set to false and value set to 8001. So now we'll call it.next twice, we'll exhaust 8000 and 8001, but then we'll log out it.next again. What do you think is going to show in the console? Our object has done set to true and value set to undefined. So the generator completes in the exact same way an iterator does. After all, it is being controlled by an iterator. You probably won't see too many generators with hard-coded values like that. Normally there's going to be some kind of processing going on in the generator function. So we'll set the local variable nextId to 7000 and we'll loop indefinitely. For each loop, we'll yield nextId++. So nextId will be post incremented. So we'll kick off our generator, we'll call it.next, and then we'll log out it.next.value. What shows in the console? 7001. So now we have a generator that will yield indefinitely, giving a new id each time. And since the generator is controlled by an iterator, we can use it in a for-up loop. For let id of process, and notice that we're executing process in order to get the iterator, and then we have our loop body. If id > 7002, we'll break out of the loop. Otherwise we'll log out id. What shows in the console? 7000, 7001, 7002. So you will see generators used in loops like this.
Yielding in Generators
Let's look in more detail at yielding within generators. So far we've seen this. We can yield the value, and that value will be returned by calling it.next. What shows in the console? Our object has the fields done set to false and value set to 8000. But we don't have to specify a value for yield. So we won't yield any value here. What shows in the console? Our object has done set to false and value set to undefined. In this generator, we're using yield in a way we haven't seen yet. We're actually going to create a local variable called results and we're going to initialize it to yield. And we'll log out result is ${result}. We'll fire up our generator and start it off with it.next, and then we'll call it.next, passing at the value 200. What shows in the console? Result is 200. So remember, to kick off the generator, we need to call it.next, and then the generator will immediately yield. We'll call it.next again, but we'll pass at a value. And you can think of yield now as an expression which will evaluate to 200, the value passed to it.next. So result is being set to 200. So here we have a similar generator, but this time we'll call it.next and we'll log out it.next(200). What shows in the console? We get result is 200 from the generator, but then, because of the last line of the code, we'll show our object. Done is set to true and value is set to undefined. Because after we called it.next passing at 200, there was no further yield in our generator. So we can use yield in lots of places where we would normally see an expression. Here we'll initialize a new array to three yields and we'll log out newArray We'll start the generator, call it.next, and then we'll call it.next with three different values, two, four, and 42. What do you think is going to show in the console? 42. So we call it.next three times to populate the array. Here's something to watch out for. Yield actually has a very low precedence, and even something like multiplication has a higher precedence than yielding a value. What do you think is going to show in the console? We get a syntax error. We want to use yield in this expression, but we have to keep in mind that yield and the following 42 need to be grouped together. So here we use parentheses. Do you think this will fix the problem? What shows in the console? We get 40. When the generator yields, we call it.next, passing the value 10. 10 times 4 is 40. The value 42 is discarded because we're not capturing the result of it.next as a function call. So here's an interesting example. We'll yield the value 42, but we're also going to yield an array. We'll kick off our generator, and we'll call it.next and log its value. We'll do that three times. What shows in the console? We get 42, the first yield, and the second yield yields an entire array, so we actually get that as our value, the entire array. And then, by the third time we call it.next, our generator is complete, so we get an undefined value. But what if we wanted to yield the values one, two, and three instead of the entire array? We can do that with generators, and we can use our asterisk symbol again. We can place it next to yield, and this is known as iterator delegation. Yield* takes something that's iterable, such as an array, and it temporarily replaces the iterator for process. So looking at how we call this generator, we kick it off, and we'll call it.next and print out the value five times. What shows in the console? We get 42, one, two, three, undefined. So using yield* or yield*, we can delegate another iterator to the generator. And once that iterator is fully consumed, that previous iterator will take over again.
throw and return
We can get better control over generators by using the throw and return commands. Let's see how they work. Here we have a simple generator that yields three values. But notice the yields are in a try-catch block. We'll start the generator, we'll call it.next and print out its value, and then we'll call a new function we haven't seen yet. We'll call it.throw, passing it a message, foo. And you can guess what throw's going to do. Then we'll log out it.next again. What do you think's going to show in the console? Well we get 9000 for our first it.next call, but then when we call throw, we'll receive an object where done is set to true and value is set to undefined. So what happened was an exception was raised and our generator caught that exception and didn't do anything with it. Finally, we call it.next again, but by that time, the generator is completed, so again we get done set to true and value set to undefined. So what if we don't have try-catch logic in our generator? Here's a similar example. We'll yield three values. We'll log out the first value, and then we'll throw foo, and then we'll log out it.next again. What shows in the console? It.next shows 9000, and then it.throw passing at foo raises the foo exception. But since the generator isn't catching it, our execution terminates. Our final call to it.next never executes. So if you do call throw on the iterator, you better make sure the generator is capable of catching anything thrown. Now there is a way to neatly clean up an iterator. We have a return function that we can call on the iterator. So here we have a simple generator. We'll start it up and print out the first value. And then we'll call our iterator.return, passing at foo. And we'll also log out it.next. Now just as a warning, Firefox is the only browser I've seen that actually has implemented return yet. I'm sure the others will catch up to this soon. What shows in the console? We get 9000 as our first it.next call. Then when we call it.return, passing at foo, we get a value of foo and done set to true. So calling return on an iterator is a clean way to wrap up the iterator and complete it, get that done flag set to true. And whatever value we pass to return will become value in our returned object. And because we did call return, we'll never get to 9001 or 9002. The final line of code results in value undefined and done being set to true because the iterator and the generator are complete.
Promises
ES6 now has native support for promises. If you've done asynchronous programming in JavaScript, you're probably familiar with the jQuery library which implements its own version of promises, or the QLibrary, which offers a popular form of promise object. And if you're new to asynchronous programming, a promise is basically an object that's waiting for an asynchronous operation to complete. When the operation does complete, the promise is either fulfilled or rejected. Let's take a look at this in action. Here we have a function doAsync and the purpose of this function is to return a promise. So we let p = new Promise and the constructor takes a function. This function is passed two arguments, resolve and reject. If we want the promise to resolve and be fulfilled, we call resolve as a function, and if some error occurred, we call reject as a function. So the first thing we'll do is we'll just log out in promise code. That'll show that this code is actually executing. And to simulate an asynchronous event, we'll call setTimeout. Normally with promises you'd be working with some kind of data transfer or network access, but setting a Timeout is very similar. So we'll set a Timeout for two seconds, and after two seconds, we'll log out resolving, and we'll call resolve. Remember resolve is passed as an argument to this function. And later we'll see calling reject, which is the other argument to the function. So, below, we'll create a local variable, promise, and we'll call doAsync, which returns that promise. So what do you think is going to show in the console? We get in promise code, so our promise does execute. Then there's a two second delay. That's for the setTimeout. And then it logs out resolving... So just by creating a new promise and passing it a function, that function will execute immediately. We don't have to do anything special, we just have to create the promise to have the code execute. And then when our asynchronous operation completes, we get our two second delay, and then it prints out resolving... And to notify the promise, you have to make sure to call resolve or reject. So here we have a very similar example, except now we're going to reject the promise. The only lines that changed are right here. What shows in the console? We get in promise code, we get our two second delay, and then it prints out the message rejecting... So if we were gathering data from a website or a web service, depending upon the results, we would call resolve or reject. So here's how we would work with promises in our application code. We would set up the promise as we did in the last two slides. So I'm not going to show all the code for doAsync. Let's just say that when we call doAsync, it'll return a promise and it will be rejected eventually. So we call doAsync to get our promise, but our application is going to need to execute some code when that promise is either fulfilled or rejected. And we use the then function on the promise to accomplish this. Then takes two arguments. The first one is a fulfilled function. This function will get executed when the promise is fulfilled, or if the promise is rejected, it'll call the second function here. So given that doAsync will be rejected, what do you think is going to show in the console? We get in promise code, then we wait for the resolution of the promise, and we get our rejected message. So because the promise was rejected, the second function executed. And here's very similar code, except this time the promise will be resolved. And this code is the same. We called doAsync and we called the then function on that promise. What shows in the console? In promise code, then we wait for the Async resolution, then we get the message Fulfilled! So when a promise is resolved, the first function to then gets called. When we write our promise code, we can resolve or reject the promise and pass it a parameter. Here we're calling reject and the parameter is Database Error. If we were resolving the promise, that object would be picked up right here by value. But since we're rejecting it it will be picked up right here by reason. So what shows in the console? In promise code, we wait for the resolution, and then we get our Rejected! message with Database Error. So whatever object we either resolve or reject, that object gets passed along to the then function. And here's a similar example, except we'll resolve the promise and we'll call resolve passing at the string OK. What do you think is going to show in the console? In promise code, we wait for the resolution, and then Fulfilled! OK. So OK got passed along as value to the then function. In this example, we're chaining then function calls. We called doAsync to get the promise, and we call a then function, and then we chain on another then function. And this is perfectly valid and we see it all the time in JavaScript. Another thing we're doing is we're only specifying one function for then. So that will be the fulfilled function. We can go ahead and leave off the rejected function. It might not be a good idea depending upon what you're doing, but for this test case, we're only interested in the fulfill function. So we log out Fulfilled! and value, and then we return a string, For Sure. And in our chain then function, we'll print out another message, Really! What do you think's going to show in the console? In promise code, we wait for the resolution, we get the message Fulfilled! OK, then we get the message Really! For Sure. So when we return a string in the then function, it gets wrapped up as a new promise, and that promise is fulfilled because the original promise was fulfilled, and that's why we get Really! For Sure as the last log string. And again, chaining of then functions is very common. In addition to calling then on a promise, we can also call catch. So in doAsync, we'll actually reject the promise this time and we'll reject it with the string No Go. What shows in the console? Well first of all I took out the console.log in doAsync, so we just wait for the resolution of the promise, and when that occurs, we log out Error: No Go. So we can think of the catch function as an error catcher. When the promise is rejected, the catch function will be called.
More Promise Features
Let's take a look at some more features for the promise object. Here we have our doAsync function again and it's similar to before. We're going to call resolve, but we're going to call a function called getAnotherPromise. There are a lot of cases where we need to chain asynchronous calls together. And this is an example of that. SetTimeout is our first asynchronous call. That'll pause for two seconds. And then, depending on that result, we might need to, our application might need to go out and get some more data or make some other asynchronous call. And that's what getAnotherPromise would do. We can pass that promise to resolve. And looking at our call to doAsync, we'll call then and pass two simple functions. We'll print out either Ok or Nope, depending upon whether it's fulfilled or not. So what do you think's going to happen? We're resolving the original promise, but then we're going to pass in another promise, and for this example, let's assume that the promise that's returned by getAnotherPromise, let's assume that one is rejected. What do you think is going to show in the console? In promise code and then Nope. So even though we're calling resolve in our initial promise, we're passing resolve a new promise. And getAnotherPromise, whatever that returns, that'll be the promise that gets picked up by our then function call. And we're assuming that that one was rejected, so we get Nope. Sometimes when you attempt to do an asynchronous call, you actually might not need to do the asynchronous call. For example, let's say we wanted to reach out to a web service to get some data. Then we realize the data's actually in cache and we can use that. We can still return a promise. We use the resolve static function, which is directly on promise. We call Promise.resolve, and we pass it some kind of information. Here we'll pass it Some String. Looking at doAsync().then, our call right here, we'll log out simple messages for fulfillment or rejection. What shows in the console? Ok: Some String. So by calling Promise.resolve, we're creating a promise that will be fulfilled with the passed value. And the opposite of that is Promise.reject. That'll instantly reject the promise, passing the object, in this case, Some Error. What shows in the console? Nope: Some Error. So we have Promise.resolve and Promise.reject in cases where we need to create a promise instantly, without an actual asynchronous call. There's another static function on promise called all. In this example, let's create two promises, p1 and p2. And we'll call Promise.all and we'll pass it an array of those two promises. Now for all, we could pass it one or more promises. In this example, we just have two. And then we'll call our then function, we'll log out Ok or Nope, depending upon if it's fulfilled or not. So with our two promises, p1 and p2, let's assume that p1 resolves after three seconds. Let's also assume that p2 resolves after five seconds. What do you think this all function is going to do? What shows in the console? We get a 5 second delay and the message is Ok. So promise.all waits for all of the promises to complete, and once they're all fulfilled, as in this case, it'll call the first function to then, our fulfilled function, and we get Ok. So here's the same exact example, but let's change our assumptions. Let's assume p1 resolves after one second, but p2 is going to be rejected after two seconds. What shows in the console? We get a two second delay and it says Nope. P1 resolved first and that was fine, but that only took one second. It waited for p2, but p2 was rejected, so it called by our second function and printed Nope. Again, here's the same exact example, but we change assumptions. Let's assume p1 is rejected after three seconds. We'll also assume that p2 is rejected after five seconds. What shows in the console? We get a three second delay and Nope. So as soon as one of the promises that we pass is rejected, our then function will get called. We don't have to wait around for subsequent promises to get resolved or rejected. The first instant we get a rejection, the call to Promise.all is finished and then gets called. There's another static function on promise called race. So this is a very similar example to the last one. Instead of calling all, we're calling race. We create p1 and p2 as promises, we call Promise.race, passing an array of all the promises we're interesting in looking at, and then based upon fulfillment or not, we'll print out Ok or Nope. So our assumptions here, let's assume that p1 resolves after three seconds and we assume that p2 resolves after five seconds. What do you think is going to show in the console? We get a three second delay and then it says Ok. So with Promise.race, it's a race to complete first. Whichever promise completes first, that's the promise that gets handled by then. So there was only a three second delay because p1 resolved first. Here's the exact same example, except let's change our assumptions. Let's assume p1 is rejected after three seconds, and let's assume p2 resolves after five seconds. What's going to show in the console? We get a three second delay and Nope. So when a promise is rejected, race will instantly end. It won't wait around for anything else to resolve. So there's only a three second delay and we get Nope for the rejection. Here's the same example again, and our assumptions are p1 resolves after four seconds and p2 is rejected after five seconds. What shows in the console? We get a four second delay and Ok. So it doesn't matter if some of the promises are rejected. As long as the first promise that completes is resolved, that's what will get returned by race and then then will be called. So we get Ok as the message. And again, these examples only have two promises, but you can have one or many promises passed to race and Promise.all.
Summary
In this module, we looked at iterators. Iterators let us iterate through an array or an object, or even a string. Plus we can write our own custom iterators. Next we looked at generators, and a generator is a special kind of function which can yield values. It can be called over and over again by an iterator. Next we looked at yielding within generators. When a generator yields, it gives control back to the calling function and it can pass a value at that point. Then when the generator is called again, a value can be passed in. So a generator is a single function that can constantly return values and accept new values with the yield statement, which is usually placed within a loop in the generator. Next we looked at the throw and return functions on iterators. This allows the iterator to throw an exception within its generator and, likewise, return can be called on an iterator to close down the generator for good. It won't return any more values. And then we looked at promises. Promises wait for asynchronous operations to complete, and then the promise object is either resolved or rejected. We use the then function on a promise to execute a function based upon the status of the asynchronous operation. And finally, we looked at Promise.all and Promise.race. These two static functions on promise work with multiple promises at the same time. Promise.all will wait for all of the promises to resolve successfully, and Promise.race will wait for only the first promise to be resolved.
Arrays and Collections
Introduction
Welcome to this module titled Arrays and Collections. My name is Mark Zamoyta. Arrays have been extended greatly in ES6. There's a lot more we can do with them and there are brand new collection classes as well. Let's take a look at what these are. We'll start off by looking at array extensions. These include the static functions added to array as well as the functions added to array.prototype. Next, we'll look at array buffers and typed arrays. An array buffer is simply a buffer of bytes, and by typed arrays we don't mean any kind of typed arrays, we specifically are talking about integer typed arrays. And they can be 8, 16, or 32 bit integers, and they can be signed or unsigned. And they're created by pointing the new type at an array buffer so we'll see how this works. Another thing we can do with an array buffer is set up a data view object. Data view gives us complete control over how to access the array buffer. Not only can we work with different sizes of integers and signs positive or negative, we can also work with the endianness of the numbers. Endian refers to whether the significant bits are stored first or last in the array buffer. There are two new classes we can use to map key value in pairs, Map and WeakMap. Normally in ES5, if we wanted to create a map we would simply use an object. The property would be the key and it's value would be the value for that key. And the difference with the new Map object now is that now we can use objects as keys. You can't do that in a regular JavaScript object. And a WeakMap holds a weak reference to an object. When an object gets deleted and garbage collected, it automatically gets removed from the WeakMap. And we also have Set and WeakSet. Sets don't have key value pairs, they just have single values and the set guarantees that those values are unique. And similar to a WeakMap, a WeakSet holds a weak reference to an object so when the object is garbage collected, it gets removed automatically by the JavaScript engine from that WeakSet. And we'll also take a look at subclassing. Many programmers wanted to be able to subclass array in ES5 and now we can do that in ES6. We can create our own class that extends array. And we can actually do that with several other objects in ES6, and we'll take a look at that too. So let's get started in this module and we'll take a look at all the extensions added to array.
Array Extensions
We have several extensions to the array object in ES6. So let's take a look at those. First of all here's the problem with ES5. We'll let salaries equal array, and we'll pass at 90,000 with the programmer assuming that 90,000 would be a salary that goes into array. We'll log out salaries.length. What shows in the console? Well the length of the array is set to 90 thousand. And that's a quirk with ES5 and arrays. If we pass array just one value in the constructor, and that value's numeric, it will create an array that large. And that's not really expected. So now in ES6 we have an array of function. What do you think happens when we run this? What shows in the console? We get one. So an array dot of is a new way of creating an array, and it does away with that quirk. If we pass it only one numeric value it creates an array that includes just that one value. So that's the length of one. Here we have a simple array, amounts set to three values. ES6 has a new function for arrays, array.from. We'll pass the amounts and we'll pass it this land expression. It's an arrow function. V goes to v plus 100. And we'll log out salaries. What do you think is going to show in the console? We get 900, 910, and 920. So array.from creates a brand new array based on amounts, and for each element of amounts it will call this land defunction. The value would be v and it will return v plus 100. So it just increases each amount by 100. Here's a similar example we're calling array.from. We'll pass it amounts and this time we'll pass the regular function, not an arrow function. And we'll return v plus this.adjustment. And notice the third parameter to array.from. It's an object we're resetting adjustment to 50. What shows in the console? 850, 860, and 870. So the third parameter to array.from, that represents an object that will become this within the function. So this.adjustment is equal to 50. So each elements of amounts will get increased by 50. So let's try that same example, except this time instead of using the function keyword to declare the function, let's use an arrow function. V goes to v plus this.adjustment. What shows in the console? We get not a number three times. You have to remember that arrow functions don't let you change the this keyword. This will always be set to the context that it appears in. So an attempt to change this with an arrow function just won't work. This.adjustment will be undefined and we get not a number for all our calculations. Here we have an arrays salaries and we'll call salaries.fill passing at 900. Fill is a new function that extends the array object. What shows in the console? 900, 900, 900. So fill fills that entire array with that one value. And here's the same example except we'll pass the second argument to fill. We'll pass one. What do you think that means? What shows in the console? We get 600, 900, 900. So the one here means start filling at array index one. And remember arrays are 0 based for indexes. so that's why we get 600, 900, 900. And here we're passing it a third parameter, a two. What do you think is going to show in the console? 600, 900, 800. So the third parameter represents an index to stop at, and it's not inclusive. Index number two would be the value 800 so we don't include that in the fill. So we're only filling index number one. So in this example we'll call fill with 900 and negative one. What do you think is going to show in the console? We have 600, 700, 900. So negative one means start counting from the end of the array. So one element from the end is 800 and that will get filled with 900. We also have a find function added to arrays. Here we'll call salaries.find. And we'll pass it a land expression. Value goes to value greater than are equal to 750. What shows in the console? We get 800, so salaries.find will scan the whole array. And for each element it will call the function. In this case we are looking for values greater than or equal to 750. And our answer is 800. Here's a similar example but now we're going to check for value is greater than or equal to 650. What shows in the console? 700, so as soon as find finds someting in the array it returns immediately. It doesn't scan the whole array and return an array. It just returns the first value it finds that fits the criteria. In addition to find we also have a find index function, which is very much the same except it will return the index not the value. Now we don't need to use arrow functions. Here we call a regular function and instead of just passing in a value we can also pass in an index, which is the current index being looked at by find index and the array, the initial array we are working with. We'll return value double equals this. And we'll also pass it in 700. What do you think is going to show in the console? We get one. So first of all the 700 refers to the this variable within the function. It can be an object or it can be a primitive type like 700 here. And in the function we'll return value is equal to this so when the value is 700 we'll get true. And 700 in the array is located at index one. We also have a copyWithin function added to arrays. And like the name says it copies data only within the array. So if we pass it a 2 and a 0, what do you think will happen? What shows in the console? We get 600, 700, and 600. So the first argument, two, refers to the destination point. Where do we want to copy data to? And we'll copy it to index two. So we know we're going to cover up this 800. And the second argument, a 0, is the source index. So that where we're going to copy from. We'll copy starting at index 0. So our answer is 600, 700, and the 800 gets replaced by 600. Our IDs array has the values 1, 2, 3, 4, and 5. And we'll copyWithin passing at 0 and 1. So we'll pass in the values 0 and 1. What shows in the console? Our array is 2, 3, 4, 5, 5. So the destination is 0, we're going to start writing to the first element. And then our source is index one. So we'll start reading from the second element, which is two. So we get an array of 2, 3, 4, 5, 5. How about this one, we'll copyWithin and we'll pass at a 3, a 0, and a 2. What shows in the console? Our array is 1, 2, 3, 1, 2. So our destination where we're going to write to is index three, we're going to start writing from index 0 and our third argument is a 2, which means we only want to copy two values, the 1 and the 2. Thus, our new array is 1, 2, 3, 1, 2. Arrays have a new function called entries. And you can see here we're using the spread operator on it. What happens when we log out the spread operator, ids.entries? What shows in the console? We get the entries of the array. And each entry is actually an array in itself. The first element is the index and the second element is the value. And notice we are using the spread operator, so all of this is stored in its own array. But we break out the individual elements. We can also call ids.keys. What do you think keys returns? Notice we're using the spread operator on it again. What shows in the console? We get 0, 1, and 2. So we only get the index values, which for an array would be 0, 1, and 2, a length of 3. And we can also call ids.values. Again we're using the spread operator. What shows in the console? We get the values of the array without the indexes. So we get A, B, and C.
ArrayBuffers and Typed Arrays
Let's take a look at Array Buffers and Typed Arrays. An Array Buffer is simply an array of 8 bit bytes. And for typed arrays we're not talking about any types, we're only talking about the numeric types. And these typed arrays exist on top of the array buffers. So we'll be able arrays of integers and floats. And lets see some specific examples of these. Here we're creating a new array buffer and we're passing it 1024. We'll log out buffer.byteLength. What do you think is going to show in the console? 1024, so we pass array buffer the length of the new byte array. And remember these are just 8 bit bytes. Here we'll create a new array buffer with the size of 1024 again. And you can see w're using bracket notation on buffer. We're setting buffer 0 to hexadecimal FF. And then we'll log out buffer 0. What shows in the console? 255, so use bracket notation on array buffers just as we would on a normal array. And hexadecimal FF is 255 in decimal. So once we have an ArrayBuffer set up with 8 bit bytes we can add a typed array on top of it and use those bytes. Here are the types for 8 bit integers. They can be signed, unsigned, and that's what Uint stands for is unsigned integer. And then a Uint8ClampedArray. It's called clamped because if we set a high value it will get clamped to 255. If we set a low value such as a negative one that will get clamped to 0. But these are the three types that we can work with for 8 bit whether signed, unsigned, or clamped bytes. We can also work with 16 bit integers whether signed or unsigned. And the same with 32 bit integers, signed and unsigned. And finally we can work with floats whether 32 bits or 64 bits. And each one of these types gets passed in an ArrayBuffer. The array of actual bytes that holds the values. Let's see some examples of these. So we create a new ArrayBuffer, 1024, and then we'll create a new Int8Array. So that's a signed integer, and we want to work with 8 bit integers. And we pass at buffer, so the ArrayBuffer is holding the actual bits. We'll assign a subscript 0, the value 0 XFF, hexadecimal FF. And we'll log out a 0. What shows in the console? We get a negative one. You have to be careful when working with these arrays. You always need to know whether the array is signed or unsigned. Since Int8Array is signed, hexadecimal FF actually refers to negative one. So here's the similar example except now we're going to use the unsigned integer 8Array. We'll assign a 0 FF again and we'll log out a 0. What shows in the console? Now we get 255. An array is only working with unsigned integers now, so there's no negative numbers involved. And here's a similar example, we'll create a new UInt8ClampedArray. So it's an unsigned integer, 8 bits but it's clamped. We'll set a 0 to negative 12 and we'll log out a 0. What do you think is going to show in the console? 0, so by working with the ClampedArray negative numbers will just be set to 0 and values over 255 will be set to 255. Here we have an example of two arrays that are using the same ArrayBuffer. We have an unsigned Int8Array and an unsigned In16Array. And the 16 again refers to 16 bit integers. We'll set a 0 to 1, but remember both these arrays are using the same underlying buffer. So even though we set a 0 to 1 we're going to log out b 0. What shows in the console? We get 1, both of these arrays whether they are 8 bit or 16 bit are using the same underlying data. Now let's try a similar example. Our setup is the same we have an ArrayBuffer and we're using that as the underlying data for an UInt8Array and a UInt16Array. But this time we'll set a1 to 1, and we'll log out b 0. What shows in the console? Now we get 256. So remember for our UInt16Array we're working with 16 bit bytes and most browsers and PC's work in little Endian mode. We'll look at little Endian and big Endian soon. But by setting a1 to 1, the high order byte results in a decimal value of 256 for the 16 bit array.
DataView and Endianness
When working with bytes and arrays of integers, Endianness is very important. Endianness refers to big Endian or little Endian. In big Endian the most significant byte is stored first. In little Endian the least significant byte is stored first. And we use the DataView object to handle Endianness when setting and getting values. Let's take a look at this. So we're creating a new array buffer passing at 1024 and then we'll create a new DataView passing at the buffer. And the DataView is like a helper object, which works with the buffer but give us a lot of different methods that we can call. And most those methods work with the Endianness of the values. Before we look at that let's just log out our DataView, dv.byteLength. What shows in the console? 1024. So the DataView takes on the characteristic of the ArrayBuffer. In this case it takes on the size. We can also have the DataView work with only a subsection of the ArrayBuffer. Here we create DataView passing the buffer, 0 and 32. We'll log out dv.byteLength. What shows in the console? 32. So the 0 refers to the start position and the buffer. We're starting at position 0. And 32 refers to the length that we want. We want 32 bytes. So here we're setting up our ArrayBuffer and DataView. And then we'll call our Dataview, dv.setUint8 and we'll pass it a 0 and a 1. Notice we're not using bracket notation. With DataView we work with methods. So we're setting an 8 bit unsigned integer at array index 0 to the value 1. But now we're still working with the same DataView, and let's log out dv.getUnsignedInt16, and we'll pass it a 0 for index 0. What shows in the console? 256. Now if you watch the last video this is pretty much the opposite. When we're working with DataViews, we're not working by default with the Endianness of the system. We're working with big Endian data by default. So that's why by setting our Uint8 index 0 to 1, we get 256 when we look at dv. getUInt16 array index 0. So here's the same example again except this time let's log out something different. We'll log out dv.getUInt16. We're still going to work with index 0 but let's pass a true flag. What do you think that will do? What shows in the console? Now we get 1. By passing true we're telling the method to use little Endian formatting. And that's important to remember. Most computer systems and browsers work in little Endian by default. But when we're working with DataViews you need to keep in mind that is works with big Endian values by default.
Map and WeakMap
We have two new classes we can work with in ES6, Map and WeakMap. Now we actually use maps all the time in JavaScript. In fact every object could be considered a map. An object is made up of properties and values. You can think of those as keys and values. But in an object the keys have to be strings or numbers. You can't use an object as a key. And that's why Map and WeakMap were created. These are useful when you have some kind of object that you need to key the Map off of. And because we're using objects as keys, ES6 provides a WeakMap. Let's say an object exists as a key in a WeakMap and then all references to that object disappear. The JavaScript engine will actually remove that reference from the WeakMap. So WeakMap doesn't hold onto that object strongly. When an object's ready to be garbage collected it will be automatically removed from the WeakMap. Let's see some examples. Let's create two employees, one named Jake, one named Janet. And then we'll create a new map, assigning that to employees. The way we add objects and values to a map is with employees.set. So we'll call employees.set, passing it employee 1 and then the value ABC. And we'll set employee 2 to the value 123. We'll log out employees.get passing it employee 1. What do you think is going to show in the console? ABC. So the Map is keyed off of these objects and the value for employee 1 is ABC. Here's the same exact example, except this time let's log out employees.size. What shows in the console? 2, so that's how you access the size of a Map with the size property. In this example our setup is the same. We'll add employee 1 and employee 2 to the Map, but then we'll call employees.delete passing it employee 2. And we'll log out employees.size. What shows in the console? 1, so that's how you delete an entry from the Map. You call the delete method passing it the object, which is the key. Again our setup is the same and let's call employees.clear. We'll log out employees.size. What shows in he console? 0, so the clear method clears out the entire Map. Here we create employee 1 and employee 2, but then we create an array of these employees and their values. So we have an array of arrays. We'll create a new Map passing at this array. And we'll log out employees.size. What do you think is going to show in the console? 2, so this is a more common way if you're working with a lot of data to create a Map. We can pass it an interable. In this case the interable is an array. In this example our setup is the same, we'll create the map with the two employees, but now we're going to log out employees.has(employee2). What shows in the console? True, so the has method on a map tells us whether or not that key exists in the map. In this example again we'll create our map the same way with our 2 employees and we'll create a variable list and we'll assign it a new array and we'll spread out employees.values. Notice values as a method, and we'll log out the list. What shows in the console? We get an array that contains ABC and 123. So the values method called on a map will give us a list of all of the values, it ignores the keys and just gives us values. And here's a similar example except we'll create an array using the spread operator at employees.entries. And this time we're going to log out list, element 0, element 1. What shows in the console? ABC, so by calling the entries method on a map, we get everything back. You can view it as getting the original array that we passed in and by using the spread operator, list 0 will refer to employee 1 and the 1 will refer to the second element of that array, which is ABC. So let's look at WeakMaps now. We'll create employee 1 and employee 2 as before and we'll create a WeakMap. Instead of using a variable we'll just directly pass in the array of employees. The thing about a WeakMap is that it holds a weak reference to the object. When one of those keys such as employee 1 or employee 2, gets garbage collected, it will automatically be removed from this WeakMap. So let's set employee 1 equal to null, and that opens it up for garbage collection. And later on, we'll log out employees.size. What do you think's going to show in the console? Well we get undefined, we can't access size on a WeakMap. And there lies in the big problem of testing and working with WeakMaps. Once something is garbage collected, we don't have a reference to it so it's impossible to check whether it exists in the WeakMap or not. If we tried to console that log employee 1, we'd get null. So you just have to put your faith in the JavaScript engine and if you get a value back that's great, but if you don't get a value back you'll never know if it was ever in there in the first place or not.
Set and WeakSet
Next, we'll look at Set and WeakSet. These collections are similar to Map and WeakMap, except they deal with single values or single objects. There's no mapping from a key to a value as in a Map. And the purpose of Set it to guarantee uniqueness of its items. And WeakSet, just like WeakMap, holds a weak connection to its objects so when the object is garbage collected it's automatically removed from the WeakSet. Let's see how we use these. So here we'll create a new Set and we'll assign it to the local variable perks. We'll call perks.add passing in car and perks.add, super long vacation, and we'll log out perks.size. What shows in the console? 2, so we add members to the Set with the add function right here. And this example is similar to the last one except we'll add another call to perks.add and we'll pass in car again. What do you think's going to show in the console? 2, so a Set guarantees that it's items are unique. So it just won't put the value car in there twice. So here we're creating a Set by passing it an array. We'll log out perks.size, what shows in the console? 3, so the constructor for Set actually can take an iterator. So here we're passing it an array which is an iterator and the 3 values get added to the Set. Here we create perks the same way with its 3 values, but now we'll create a new set passing it perks and we'll assign that to new perks. We'll log out newPerks.size. What shows in the console? 3, so again Set can take an iterator and we can see the perks, a Set, is already an iterator. So new perks becomes a copy of perks. So here we're setting up perks again in the same way with 3 values, and we'll log out perks.has, jet and we'll log out perks.has, cool hat. What shows in the console? True and false. So the has method lets us know whether or not the Set contains the value and there is a value jet, but there is no value for cool hat so we get true and false. Here we create a new Set with the values car and jet. Now in the Map class, we had access to keys and values of the Map, and we also had access to entries of the Map. But here we're going to try to access those same functions on a Set. What do you think's going to show in the console? Well when we deconstruct perks.keys, we get the keys, car and jet. Now, you wouldn't think a Set would have keys and values but these methods do work on a Set. It just treats the item as both the key and the value. For perks.values when we deconstruct that, we also get car and jet, and for perks.entries, here it's more clear that when we deconstruct it, the key and the value are actually the same. We get car and car, and jet and jet. And again, these make a lot more sense on a Map rather than on a Set. So here we're creating a new Set and we're passing it two objects. And the objects are the same, they both have an ID being set to 800 and we'll log out perks.size. What shows in the console? We get 2, even though the objects are identical they're still separate objects specified as two unique object literals. So they're both put in the Set. And what about this, we'll create a new Set and we'll pass it 1 as a number, and then 1 in quotes. We'll log out perks.size. What do you think's going to show in the console? We get 2. So primitive types can be put in a Set and the number 1 and string 1 are considered different and they both get stored in the Set. Next, we'll take a look at the WeakSet. In the prior video, we took a look at WeakMap and we saw how the JavaScript engine would keep a weak reference to an object. When that object was destroyed and garbage collected, it would be removed from the WeakMap and it's the same with the WeakSet here. So here we create a new WeakSet and we'll pass it an array of 1, 2, and 3, assigning it to perks. We'll log out perks.size. What shows in the console? Well, we get a runtime error. WeakSet.prototype.add that comes from the constructor, key is not an object, and the key word here is object. We need objects in the WeakSet, we can't use primitive types. So here we'll make some object literals. P1 will have a name property set to car, p2 will have a name property set to jet. We'll create our new WeakSet passing it p1 and p2 as an array. We'll log out perks.size. What shows in the console? We get undefined, so in a similar way to WeakMap we really don't get access to what it holds. The JavaScript engine is in charge and as things are garbage collected, they get removed and it does not give us access to the size property at all. So this is similar to the last one, we'll create a new WeakSet with p1 and p2. This time we'll log out perks.has, p1. What shows in the console? True, and this is how we would use a WeakSet. We would query it with the has method to see if the object existed in the WeakSet. If it didn't, we would either need to fetch it or add it to the WeakSet. So here we'll create our WeakSet in the same way as before. P1 and p2, and we'll set p1 to null. And let's assume we wait for a garbage collection cycle after we set it to null, and then we log out perks.has, p1. What shows in the console? We get false, so again we're not really testing what we think we're testing like we saw with the Map. Once we lose a reference to p1, it's going to automatically be removed from the WeakSet so we have to trust JavaScript and there's no real way to check that. So here, we're bogusly looking for null. We call perks.has with a null value. Now if you did want to get a look at that WeakSet you would need to use some kind of debugger such as the debugger built into Chrome. But from the source code, there's no way to take a look at it.
Subclassing
Let's take a look at subclassing in ES6. It would have been really useful in ES5 if we were able to extend an array. That was a requested feature in ES6 and we got it. So in this video, we'll take a look at extending an array but we can also subclass other objects. Let's take a look at the compatibility chart. So I'm here at the chart that we looked at in the introduction to this course. You can go to kangax.gethub.io/compattable/es6 right here. And if we scroll down a bit, we can see the subclassing section. So looking here, we can see that we can subclass array and that's what we'll look at in this video, but we can also subclass the regular expression object, function, promise. Let's open up miscellaneous subclassables, and we see that we can also subclass bullion, number, string, map and set. And again. subclassing means extending these objects and adding our own properties and methods to them. Now, the important thing to note here is look at this huge red section. And this means that none of the compilers and polyfills are able to properly do subclassing. It's a very complex procedure that needs to be built into the browser. I can do it fine running on Edge as you can see by all the green here, but if we were going to use Babbel or Tracer or TypeScript, we wouldn't be able to do this subclassing. So before you work with subclassing, just make sure that your target platform will be able to handle it. So let's take a look at subclassing the array object. Here we'll create a class called perks and it extends array. So now we're subclassing array. We'll call perks.from passing it an array of three numbers and we'll assign that to a. We'll log out a, instanceof perks. What shows in the console? True, so the from method right here comes directly from array, and because we're extending array, we now have it on perks. So a is an instance of perks. So here we have the same example except this time let's log out a.length, a property. What shows in the console? 3, so again because we're extending array we get access to array's property, length. So here's a similar example, perks will extend array and we'll create a in the same way, perks.from passing it five numbers but now we're going to call a.reverse and assign that to new array. And reverse is a new function on array that we already saw, it just reverses the elements. And we're going to log out new array instanceof perks. Now we already know that the variable a is an instance of perks, but if call a method on it from array, will that also result in an array that's an instance of perks? What shows in the console? True, so even though we're calling the reverse function on array's prototype, the result is still an array that's an instance of perks. So we really are extending array and we're keeping our own data type, perks. Now here's the same example except let's log out new array instanceof array. We just saw that new array was an instance of perks but is it also an instance of array? What do you think's going to show in the console? True, so new array is both an instance of perks and array. So here's an example of why subclassing is useful. We can add properties and in this case we can add a method to perks. So essentially, we're extending array by adding a new method to it. So if we look at this sum method, we'll set total to 0 and then we'll call this.map and remember we need to be using this in order to access properties and methods on perks. So we call this.map and we'll take an input of v for the value. V goes to total += v, so it will just sum up all the elements of the array, and will store it in total. Then we return total, so we'll create a new perks instance, we'll call perks.from passing it the three values again. And this time we'll log out a.sum as a function call. What shows in the console? 30, so it simply summed up the values 5, 10, and 15 and again this is the usefulness of subclassing. Adding new properties and in this case, adding a new method to perks which is extending array.
Summary
In this module we looked at array extensions. There are several static methods added to array as we can see here array.from and there are many other functions added to array's prototype. We looked at array buffer and typed arrays. It's very easy now to create an array buffer of bytes in order to hold some kind of binary data such as videos, or pictures, or music, and we can used typed arrays to access that data in the array buffer. We also looked at DataView and Endianness. With the new DataView class, we can instantiate it and get complete control over our array buffer and how integers are stored. We looked at Map and WeakMap, and while we knew that we could already use a JavaScript object as a map, we can use the map class in order to create a map that has an object as a key. And a WeakMap holds a weak reference to that object so when the object is garbage collected, it's automatically removed from the WeakMap. We also looked at Sets and WeakSets. A Set holds single values and guarantees that they're unique. And like a WeakMap, a WeakSet is also controlled by the JavaScript engine in that objects are deleted from it as they're garbage collected. And finally we looked at subclassing. Now we can subclass array and whole host of new ES6 objects. We saw that we can extend array and add new properties and methods to it.
The Reflect API
Introduction
Hi, my name is Mark Zamoyta, and in this module, we'll talk about the Reflect API. This API is brand-new to ES6, and it's a major addition. Now of course, in JavaScript, we have lots of different ways to work with objects and functions and there's various syntax for these different ways. But what the Reflect API is, is it's a single object called, Reflect, that lets us use function calls to perform many of the operations we perform in JavaScript. And let's see what we'll cover in this module, and I'll give you some examples. We'll start off by looking at Object Construction. Obviously, we can create objects in JavaScript with the new keyword, or by creating an object literal, or by calling object.create. And now there's another way. We can use the Reflect API to construct an object. Likewise, there are many ways to call a method. We can use parentheses, the call function, the apply function, and now we can also use the Reflect API for that. We can work with Prototypes with the Reflect API, Getting them and Setting them, and there are several API functions that we can use to work with Properties, as well. Also, we can use the Reflect API to work with object Extensions and adding new Properties to objects, or preventing that altogether. So it seems like there's not anything really new here. It's just a different API to work with all these different operations we perform on objects and functions. And that's extremely valuable when it comes to the Proxy API. And we'll be learning about Proxy in the next module of this course. But in order to work with Proxy and understand Proxy, we need to understand this Reflect API first. And we'll be covering all of its functions in this module. Another use for the Reflect API is if you have some kind of DSL, or Domain Specific Language. It's a lot easier to work with this single reflect object than to work with lots of different syntax that JavaScript uses. So we'll cover all the functions on Reflect, and there's not a lot of them. And we'll start off with Object Construction.
Construction and Method Calls
Let's take a look at using the Reflect API for Object Construction, and also for Method Calls. Now before we look at the API functions directly, let's log out, (typeof Reflect); What shows on the console? It's already an object. So we don't have to create a new one, we can just use Reflect the same way we use something like math. Here's the first API function we'll look at, Reflect.construct and looking at the arguments, target is the function we want to construct, argumentsList is an array of the arguments to pass to the Constructor, and we can pass it an optional newTarget. This is the new dot Target value that we saw earlier in this course. So we're actually able to change that value, by using Reflect.construct. So we'll create a class, Restaurant, and we use the Reflect API to construct the new one. We'll assign it to r, and we'll log out (r instanceof Restaurant). What do you think's going to show on the console? True, so this is a similar way to calling, new Restaurant, but we can use the Reflect API with Reflect.construct. So here we'll create a Restaurant class, and we'll add a Constructor. We'll pass it a name in a city. We'll log out ( ${name} in ${city} ); again, we'll use Reflect.construct, we'll construct a Restaurant, and we'll pass it this array. What shows on the console? Zoey's, and Goleta. So remember that when we pass arguments with the Construct method, they need to be array elements. So again, we'll have a Restaurant class with a Constructor, same as before, except this time we'll log out, ${new.target}, and actually print out that function. Now we'll have a stand-alone function called, restaurantMaker, and within it, we'll just log out, ('in restaurantMaker'). So now we'll call Reflect.construct, we'll pass it, Restaurant, and we'll pass it our arguments, but we'll also pass it our function restaurantMaker. What shows in the console? So new.Target gets printed out, and it's the function restaurantMaker. So remember that the third argument to Construct is going to be the new value of, new.target. So we have total control over what new.target gets set to. Also notice that restaurantMaker, as a function, never got called. There's no output that says, in restaurantMaker. But we could call that, now that we have access to it with new.target. So let's look at actually calling a function with the Reflect API, we can call Reflect.apply. So this would cover calling a function with parentheses, or with the apply function, or with the call function. Any way to invoke a method, Reflect.apply applies to it. So we pass it target, the function we want to call, we can set the, thisArgument, and we also pass it an array, which is the argumentsList. So we have a class Restaurant, and in the Constructor, we're setting, this.id to 33. And we have a show method, which is simply going to log out (this.id); we'll call Reflect.apply, passing it (Restaurant.prototype.show, so we're digging down into the prototype to pass it the actual function itself, and then we'll pass it an object with id set to 99. What shows in the console? We get 99, remember that when we call Reflect.apply, the first argument will be the function to call, but the second argument is what the, this object gets set to. In this case, within the, this object, id is set to 99. And notice that we never even instantiated a restaurant, Reflect.apply is very low-level. We can call any function within a class, even though we haven't instantiated an object. We can pass it the, this value. See, here's similar example. In the Constructor, we'll set, this.id equal to 33. We'll create a function, show, and we'll pass it a prefix, and we'll log out (prefix + this.id); we'll call Reflect.apply, we'll pass it, Restaurant.prototype.show, and we'll pass it an object with an id set to 22. And the third argument to apply will be an array, which right now only contains the value, REST: What do you think's going to show in the console? We get, REST:22. So remember that the third argument to Reflect.apply is an array of arguments to send to the function.
Reflect and Prototypes
Now let's look at the Reflect API and its use with Prototypes. We need to be able to Get the Prototype of an object, and we also need to be able to Set the Prototype of an object. There's an API method called, Reflect.getPrototypeOf, and we pass it the target. That's the object or function we want to Get the prototype of. So let's create a class Location, and in the Constructor, we'll just log out that we're constructing Location. We'll create a class Restaurant that extends Location. And we'll log out a call to, (Reflect.getPrototypeOf{Restaurant}); What do you think's going to show in the console? We get a Constructor function that logs out ('constructing Location'). So remember when we're working with classes, the Constructor function is actually the prototype. So the prototype of Restaurant is actually this Constructor function right here. And we can also call, Reflect.setPrototypeOf, to set the Prototype of a target which is a function or an object. We pass it target, and we also pass it the prototype object. In this example, we have an empty class Restaurant, and we'll create a simple object literal set up which has a single function, getId, which returns 88. We'll instantiate Restaurant, by calling, new Restaurant, and assigning it to r, and we'll call, Reflect.setPrototypeOf, and we'll pass it (r, setup); We'll log out (r.getId). What shows in the console? 88, so obviously, Restaurant doesn't have a getId function, but by calling, Reflect.setPrototypeOf we can give Restaurant a prototype. So now it does have a getId function, which returns 88.
Reflect and Properties
Let's take a look at the Reflect API and Properties. There are many different operations we perform on Properties, Getting them, Setting them, Deleting them, so let's take a look at the Reflect APIs that let us get access to Properties. First, there is Reflect.get, and this will Get a property's value. We pass it the target object, and the propertyKey, the name of the property, and an optional receiver. The receiver is the object that this will be Set to, in case we have a Getter. So we'll create a Restaurant class, and in the Constructor, we'll set, this.id = 8000, we'll instantiate Restaurant, and assign it to r, and we'll call, Reflect.get, passing it r, and the value, 'id'. What does you think's going to show in the console? We get 8000, so to get a simple property on an object, we call, Reflect.get, passing it the object, and the value of the property name we want to Get. Here's an example where we have a Getter. In Restaurant, we have a Getter function for id, and we'll, return this._id; notice that in the Constructor, we're setting, this._id = 9000; so underscore id is just a backing field, and we want the users to use the property, id to call this function. So we'll instantiate Restaurant, and we'll call, Reflect.get, passing it r, 'id' as a String, and an object where, underscore id is Set to 88. What shows in the console? 88, so remember the third argument to Reflect.get is going to be the value of, this, within the function. And for the Get operation, there would have to be a Getter, and that's what we have here. Next, there's a Reflect.set function. And this will set a property value. We pass it target, which is the object, the propertyKey, the name of the property, and the value we want to set that property to. And again, in case there's a Setter, we'll take the receiver object, and that will become, this, within the Setter function. So we'll create a Restaurant class with a simple Constructor that set, this.id = 9000. We'll create a new Restaurant, and we'll call, Reflect.set, passing it, r, 'id', and the value 88. We'll log out (r.id); What shows in the console? 88, so we successfully called Reflect.set, to Set, r.id = 88. And if we look at Restaurant, it's changed. Now the Constructor sets, this._id = 9000, and we have a Setter for id, which gets passed a value. We set, this._id = value, so we'll create a new Restaurant, assign that to r, and now let's create an alternate object. We'll call it, alt, and it'll have id set to 88. Notice that it's not underscore id, it's just, id, being set to 88. We'll call, Reflect.set, we'll pass it, r, underscore id, 88, and our alt object. We'll log out, (r._id), and then we'll log out, (alt._id); What do you think's going to show in the console? 9000 and 88, so when we log out (r._id), we're actually Getting the value straight from the Constructor, nothing changed on this. And the only change that was made, was made to the alt object, when we log out (alt._id), we get 88. If we look back at our Reflect.set function call, we're Setting this to the alt object. So, this, in the Setter, refers to the alt object. And that's the fourth argument to Set. We can easily look into an object and see if it has a certain property. We call Reflect.has, we pass it the target object, and the propertyKey, the name of the property. So we're creating a class Location, and in its Constructor, we'll set, this.city to Goleta. We'll also create a Restaurant class which extends Location. In the Constructor, we call, super, and always remember to do that, and we'll set, this.id = 9000; we'll create a new Restaurant, and assign it to r, and we'll log out, (Reflect.has, passing it r, and id. We'll also log out, Reflect.has, passing it, r and city. What shows in the console? True, and true, so a restaurant does have a property id, set right here. And it also has a property called, city, which was set in the Constructor of the extended class. So that's why we get true, and true. We can also call Reflect.ownKeys, passing it an object. ownKeys will return an array of all of the properties on the target. So here's the same example from before. We'll create class Location, and we'll create class Restaurant, which extends Location. We'll create a new Restaurant, and we'll log out, Reflect.ownKeys, passing it r, the Restaurant. What shows in the console? We get an array with the values, city and id. So those are all the valid keys we have for Restaurant, city and id. And we get that by calling, Reflect.ownKeys. We can also define a property using Reflect. And this is similar to calling Object.defineProperty. And you'll notice on some of these Reflect API calls, they are rather analogous to some of the object function calls as well. So we call, Reflect.defineProperty, passing it the target object, the new propertyKey we want to set, and an object of attributes for that property. So we'll create an empty class Restaurant, we'll instantiate it, and we'll call, Reflect.defineProperty, passing it, r instantiated Restaurant. We'll also pass it, id, and we'll pass it a configuration object, which sets value to 2000, configurable to true, and the enumerable to true. We'll log out r, and using bracket notation, id. What shows in the console? 2000, so we successfully added the property, id onto r Restaurant, and if you want to know more about defineProperty, it's very similar to what you used with Object.defineProperty. And you can look that up on MDN, or I go into it in detail on my prior course, Rapid JavaScript Training. There's another API method called, Reflect.deleteProperty, we pass it the target object, and the propertyKey of the property we want to delete. So here we'll create an object literal, rest, with an id of 2000. We'll log out, (rest.id); we'll call, Reflect.deleteProperty, passing it, rest and id, and we'll log out, (rest.id), again. What shows in the console? 2000, and then, undefined. So at first, id is definitely set to 2000, but by calling, Reflect.deleteProperty, we can just get rid of that property altogether from the object. When we attempt to view it again, it's undefined. So in addition to creating a property, we can also call, Reflect.getOwnPropertyDescriptor, and get the descriptor for any property. We pass it the target object, and the propertyKey. So here we'll create an object literal called, rest, with an id set to 2000. We'll call, Reflect.getOwnPropertyDescriptor, passing it the object, rest and id. We'll log out, d, which is what gets returned from Reflect.getOwnPropertyDescriptor. What do you think's going to show in the console? We get our descriptor object. Configurable is set to true, enumerable is true, value is 2000, and writable is true. Again, this is very similar to, Object.getOwnPropertyDescriptor.
Reflect and Property Extensions
Let's take a look at the Reflect API and Property Extensions. Normally in JavaScript, we can always add a new property to an object. We can just specify the property on the object, and set a value, but sometimes we might want to lock an object down to where it can't be extended like that. Sometimes, if our source code tries to add a new property, we might want it to throw an error. So there are two functions to work with property extensions to an object. The first is, Reflect.preventExtensions, passing it the target object. And this will prevent us from adding new properties to the object. We'll create an object literal, rest, with id set to 2000. We'll set, rest.location equal to, Goleta. And notice that, rest, has no location property to start with, and we'll log out, (rest.location); What shows in the console? Goleta, so this is a simple example of how an object literal is naturally extendable. All we had to do was specify, rest.location, which is a completely new property, and it gets added to, rest. But here we have the same object, rest, but we'll call, Reflect.preventExtensions, passing it, (rest). Now we'll try to set, rest.location equal to Goleta, and we'll log out, (rest.location); What do you think's going to show in the console? Now we get, undefined. So by calling, Reflect.preventExtensions, passing it an object, we can make sure that no more properties get added to that object. We can also test to see if an object is extensible or not. We call, Reflect.isExtensible, passing it the target object. So we'll create our object literal again, rest, with an id of 2000, and we'll log out, (Reflect.isExtensible(rest)); then we'll call, Reflect.preventExtensions, passing it the (rest) object. And again, we'll log out, (Reflect.isExtensible(rest)); What shows in the console? True and False, so the first call to Reflect.isExtensible results in, True, and that's how JavaScript works. When we have a new object literal, it is extendable. But then we call, preventExtensions, on it, so the next call the Reflect.isExtensible, passing it, (rest), results in, False.
Summary
In this module, we looked at the Reflect API. We started by looking at Object Construction, with Reflect.construct. So instead of using object.create, or the new keyword, we can now use Reflect.construct. We looked at calling methods, with Reflect.apply. And this can take the place of call or apply or simply calling a method with parentheses. We took a look at calling, Reflect.getPrototypeOf, and, Reflect.setPrototypeOf, to work with prototypes in the Reflect API. And there were a lot of functions on Reflect for Properties, we can get a property, set a property, check out if an object has a property, get all of the keys for a property, with Reflect.ownKeys, we can define new properties, we can delete properties, and we can call, Reflect.getOwnPropertyDescriptor to get the descriptor of a property. And lastly, we looked at Property Extensions, the ability to extend an object with new properties. And we can call, Reflect.preventExtensions to prevent new properties from being added, and we can test to see if an object is extensible, by calling, Reflect.isExtensible. So now that we know all about the Reflect API, let's put it to use in the next module. We'll be looking at the Proxy object, which is also brand-new in ES6, and Proxy is a great use of this Reflect API.
The Proxy API
Introduction
Hi, welcome to this module titled, The Proxy API, my name is Mark Zamoyta. Proxies are another new concept in ES6. What a proxy is, it's an object that wraps another object, or wraps another function. And with the proxy, we can monitor access to that function or object that's being wrapped. And there are a lot of uses for this. We can use this for security in our application, we can use it for profiling, determining how long functions run, and logging things out, or just creating some kind of security logging system. And because this is such a new concept in JavaScript, many more uses and patterns are going to be developing on the Proxy API. So let's see what we're going to be covering in this module. We'll start off by Defining Proxies. There's certain terminology that goes along with Proxies, and we'll see how everything fits together and how the Proxy is actually used. Next we'll look at Available Traps. Traps are simply the Handler functions that intercept calls to the Target object. And we'll see that the Traps that are available are the same functions that are available in the Reflect API. So if you haven't watched the previous module on Reflect, you should go ahead and watch that and make sure you understand what can be trapped by a Proxy. We'll also take a look at some of the things that can't be trapped, but those are pretty minor compared to what we can do with it. Next we'll look at the Get trap. Our Proxy can intercept a Get command in order to Get the value of a Property, so we'll see how that's done. And we'll also see how we can use a Proxy to intercept calls to functions. By using a Proxy, we don't have to call the function at all, we can check security information, or call some other function, depending upon the status of our application. And then we'll look at Proxy as a Prototype. As I said earlier, lots of patterns are going to be developing for the use of Proxies, and understanding how it could be used as a Prototype on an object is worth noting. It's an interesting use of the Proxy object. And finally, we'll look at Revocable Proxies. Once you create a Proxy and hand it off, you might want to cancel or revoke the Proxy altogether. Maybe some security information changed, or maybe some data changed in your application, and you no longer want to give the user access to that Proxy, we can revoke it. We'll see how to do that. So in the next video, we'll take a visual look at what a Proxy actually is.
Proxies Defined
Let's take a look at what exactly a Proxy is, and how we can work with it. So let's go over some of the Proxy Terminology we'll run into here, and also on sites like MDN, the Mozilla Developers Network. Let's say we're starting off with some kind of Target object, or it could be a function. And this is the Main object and Main unit that we work with in JavaScript. It's some kind of object or function, usually. Now what if we had some kind of secure information within this object, and we didn't want other systems or other developers to work with it directly. Well, we can wrap that object or function within a Handler Object, and the technical term for this object is, Handler. But in reality, this is the Proxy, this is the object that we're going to give other people access to, to work with our Target object or function. So we wrap the Target in a Handler, which is effectively the Proxy object. Now, the Handler will have Traps, and the Traps deal with access to the Target object or function. For example, what if we wanted to Get a Property from the Target object. Well, there's a Get Trap, and we can intercept these Get calls to the Target object. Likewise, there's a Set Trap. If we wanted to Set a Property, we can call a function first, and validate the value, or check security, or perform any other JavaScript operation, or execute any other JavaScript source code. And there's an Apply Trap for calling functions. And if you've noticed, all of these Traps are exactly what we learned when we learned the Reflect API, so this is the main use of the Reflect API, is to work with Handler objects, or Proxies. So let's take a look at some scenarios of how these Traps would get used. Let's say we have our regular JavaScript source code, and we have our Proxy all set up to wrap up this Target object. The source code is going to want to Get a Property off of this Target object, but that call to Get the Property gets trapped by the Get Trap. And at this point, our Proxy object can perform any code it wants. Maybe it doesn't want that source code to get access to that Property value. In which case, it can just return some kind of error, or some dummy value. And what if the source code wanted to Set an object. Again, we can specify a function, which can do some kind of validation, or checking, or security checking. And if everything's okay, it can go ahead and pass that value into the Target object. And that value will come back out of the object, and get returned to the source code. So in this case, our Set Trap just passed everything through to the Target object. It went ahead and Set the Target Property. And this is where the Reflect API comes in. In our Set Trap, we just go ahead and use the Reflect API to set the value on the Target object for us. So of course, in addition to Get, Set, and Apply, we can perform any operation that we already learned about on the Reflect API. And this covers pretty much everything we could do with the Target object or function. And there are a few minor things that we can't do, and we'll look at that in the next video.
Available Traps
So let's look at the Available Traps that we can use on the Proxy API. And again, these are very analogous to the Reflect API function calls. There's handler.construct(), for construction. And again, Handler is just a reference to the Proxy object. There's handler.apply(), for function calls. Handler.getPrototypeOf(), and setPrototypeOf, to work with Prototypes. There's get, set, has, ownKeys, and those all work with Properties on a Target object. There's handler.defineProperty(), delete.Property(), and getOwnPropertyDescriptor(). Again, those work with Properties. And there's handler.preventExtensions(), and isExtensible(). So these are all the Traps that we can Set on a Proxy object. And there are a few things that we can't really trap, but they're minor. So sometimes when we use Objects, there are certain things that we can't really trap and see happening. For example, what if we use an object in a comparison, with the double equal symbol or triple equal symbol, identically equal to? There's no way for us to tell it's being used that way. Likewise, we can't see if it's being used with typeof and instanceof, and we can't see some Operations on the object, such as the plus operation. And we can't see if a String is wrapping it, or if anything else is wrapping it up. But the vast majority of object usage will be trappable.
Get by Proxy
So I won't cover all the Traps, but we'll cover trapping an object, and also trapping a function. We'll start by trapping Property access with Get. So we'll look at using a Proxy with the Get command. So we'll create an Employee Constructor function, we'll Set, this.name to Milton Waddems, and, this.salary to zero. We'll create a new Employee(), and assign it to e. And then we'll start working with the Proxy API. And we need to use the new keyword with Proxy. So we'll call new Proxy, and we'll pass it the Target that we want to proxy, which is e, the employee. So the Target is the first argument to proxy, and the second argument is an object literal that contains our Handler functions, our Traps. And the first thing we want to Trap here, is the call to Get. So when we try to access a Property, e, this Get function will kick in. And the arguments to Get are target, prop, for Property, and the receiver. That's what the, this, value will be Set to. But in this case, we're not actually going to access the e value at all, the Target, we're just going to return a String that says, Attempted access plus prop, the Property name. And we'll log out, (p.salary). What you think's going to show in the console? Attempted access, salary. So nothing got changed here. Our Proxy intercepted the access to the salary property, and it didn't do anything, it just returned a message. So here we're setting up the Employee the same way, we're using the variable e, and again, we'll Set up a new Proxy, passing it e, and for our Handlers, we'll Set Get to this function. This time, we'll call the Reflect API. We'll return Reflect.get, passing it target, prop, and receiver, those are the same three values that were passed into this function. We're just passing them along now, to Reflect.get. Again, we'll log out, (p.salary); What shows in the console? Zero, so here's an example where the Proxy is only passing through the Get function call to the Target object. And of course, we could put a lot of logic in here, if we wanted to. Maybe only certain users are allowed to access the salary, or maybe no one's allowed to access the salary, but they can access other fields. Let's take a look at the next example. So here the Set up is the same, but let's look at our Get function again. Here, we're going to check and see if prop is identically equal to salary. And if it is, we're going to return, Denied. Otherwise, we're going to return, Reflect.get, passing it the same arguments that were passed into this function. We'll log out, (p.salary); and we'll also log out, (p.name); What shows in the console? We get, Denied, for the first log statement, and Milton Waddems, the name, for the second log statement. So this is where we add our validation logic, or security logic, or whatever other reason we're using a Proxy for, we add it right here. And in this case, we don't let anyone access salary. But we do let them access every other field.
Calling Functions by Proxy
Now let's take a look at Calling Functions by Proxy. Before, we were looking at object access, so the Target was an object. Now the Target will be a function. So we have a function, getId, which returns 55. And let's create a new Proxy. Notice that now, the first argument is getId, the function. And for our Handlers, we'll Handle the Apply method that comes in. This takes a target, the this argument, and the argumentsList, as an array. And we're just going to go ahead and return a call to Reflect.apply, passing it the same target, thisArgs, and argumentsList. So we have our Proxy, and let's call it as a function and log it out right here. What do you think's going to show in the console? 55, so even though we're not directly calling getId, we're calling p, p is a Proxy for getId. And it's not doing any special processing. It's just going ahead and passing our three arguments through using the Reflect API. So I'm not going to be covering all of the Reflect API, and all of the Traps that we can set. And the rule is that any API function in the Reflect API can be used as a Trap. And now we've seen how to use it with objects in a previous video, and with functions, in this video.
A Proxy as a Prototype
So Proxies are very new to JavaScript, and programming in general. And a lot of patterns are going to be emerging, and a lot of new uses for Proxy are going to be used. Obviously, we can use some for things like security, or for monitoring, or profiling function calls. And if you look around on the internet, there are a lot of specialized cases for them. One interesting pattern that might emerge is using a Proxy as a Prototype. And let's see what I mean by that. Here we have an object literal that we're assigning to t. We're Setting a tableId equal to 99. Then we'll create a new Proxy. And we're creating this Proxy on an empty object. We're not Setting in a Proxy up on t right now. And in this example, all we're going to process is the Get function. So if we try to Get a Property, we'll just execute this code for now, return 'Property' plus prop plus 'doesn't exist'. So we have a Proxy on this blank object, but now let's set the PrototypeOf t equal to our Proxy object. So we'll log out, (t.tableId); and then we'll log out, (t.size); What do you think's going to show in the console? Our first log statement results in 99. So the tableId of t is still Set to 99. But then we try to access t.size, which doesn't exist on t, and that's where our Proxy kicks in. Because our Proxy is now the Prototype. So it prints out, Property size doesn't exist, which is the message from our Proxy. So how is this useful? Well, it gives us control over the Prototype mechanism. Instead of working like a normal Prototype, now we can execute any code we want. So just by accessing the Prototype, now we can actually log that out that the Prototype was accessed, we can apply security to it, we can shut down the application, or we can log out more information about the application. And there could be a lot of uses for this use where the Proxy is actually used as a Prototype.
Revocable Proxies
There's one other feature of proxies that we'll go over, and that's Revocable Proxies. When we create a Proxy, and we hand that Proxy off for someone else to use, or some other subsystem to use, that Proxy is going to exist forever. There needs to be a way to revoke it. What if data changes, or if security settings change? We want to be able to shut down that Proxy, and not have it be used any more. And let's see how we do that. We call, Proxy.revocable. So it's a static method directly on Proxy. And let's see an example of this. So we have an object literal, t, with tableId set to 99. We'll call, Proxy.revocable, to create a revocable proxy. Notice that we don't use the new keyword any more when we're creating these. We'll Get the Proxy returned. And we'll see that in a second, what exactly gets returned. So we pass it the target, t, and we'll pass it an object literal of our Traps. And here we'll Trap the Get function. All this Get function is going to do, is call Reflect.get, passing it the same arguments that were received, and we'll just add 100 to it, just to change it up a bit. So when we call Proxy.revocable, we get an object returned, and this object contains two Properties, Proxy and Revoke. And we're going to deconstruct this into new variables, which are called the same thing, Proxy and Revoke. Proxy is our new proxy object, and Revoke is a function that we'll call in order to revoke this Proxy. So we definitely want to hang onto that Revoke function, along with the Proxy. So now we'll call, console.log, passing it, (proxy.tableId); and remember, the Target for this Proxy is this t variable, up here. Then we'll call the Revoke function, and again, we'll call, (proxy.tableId); and log that out. What shows in the console? We get 99 for our first log command. So everything's working fine. But then, after we call Revoke on the Proxy, it's not going to work any more. We get an error, Property size doesn't exist. So by revoking the Proxy, we're better able to manage security, or force the user of this code to grab another Proxy. And the key trick here is, don't use the new keyword. Revocable is a static method directly on Proxy. And also, we need to make sure that two values are getting returned as an object. And here, we're just deconstructing them into Proxy, and Revoke.
Summary
In this module, we looked at the Proxy API. We started off by defining Proxies, and going over the terminology that's used in a lot of the documentation for it. The object or function that we want to wrap is called the Target. And the wrapper itself is called a Handler. And that's also commonly called the Proxy. And the Handler is made up of Traps. A Trap is one of the operations on an object or function that we can intercept and execute our own code. And if we need to, we can just pass along the call to the Target object, using the Reflect API. Next, we listed out all the available Traps, which coincide with the Reflect API function calls. We took a look at the Get Trap on the Proxy. We were able to intercept calls to look at a Property, and we looked at intercepting calls to functions. We can do that with the Apply Trap. Next we looked at creating a Proxy on a blank object, but then using that Proxy as a Prototype. This is a way to intercept the Prototype mechanism in JavaScript. You can create your own form of doing inheritance, and you could also possibly make up a multi-inheritance Prototype. Finally, we looked at revoking Proxies. Once we hand out a Proxy, something might change where we want to revoke it, or make it invalid. And we can do that by creating the Proxy with, Proxy.revocable, rather than using new Proxy. So that wraps up this course, Rapid ES6 Training. You've been through all the major syntax changes, all the extensions to objects, and all the new objects that we can make use of. So I wanted to end off with what I think is the most valuable resource when you're working with ES6. And it's this compatibility table. I'll scroll it to the right a bit. And we can see that Edge browser implements about 85% of ES6. The latest version of Firefox implements 90%. The latest version of Chrome implements 96%. So, ES6 is there and ready to be used, if you're going to be using the latest browsers. If you have to rely on compilers, polyfills, transpilers, you have a much smaller subset of ES6 to work with, and you can see the values here. Babel and Core JS implement 74%, TypeScript and Core JS implement 60%, and if you're using Traceur, that's at 57%. And if we scroll way over in this chart, we can see mobile devices, and these have a long way to go. IOS9 is only at 53%, and Android 5.1 is at 29%. So if you're going to be working on mobile, most likely, you should be using a transpiler. This chart is a tremendous resource, and before you implement something in ES6, you should know your target browsers, and you should consult this chart. Thanks for watching.
Course author
Mark Zamoyta
Mark started in the developer world over 25 years ago. He began his career with a Bachelor of Science in Computer Science from St. Johns University. After spending 10 years on Wall Street working...
Course info
LevelIntermediate
Rating
(317)
My rating
Duration4h 16m
Released5 Apr 2016
Share course