From 371a4aa5d8b529aa369c8535885d460a6b4896a3 Mon Sep 17 00:00:00 2001 From: albertomontalesi Date: Wed, 8 Sep 2021 19:06:32 +0700 Subject: [PATCH] 5th edition --- README.md | 10 +- ebook/03_default_function_arguments.md | 7 + ebook/10_object_literal_upgrades.md | 2 +- ebook/11_symbols.md | 26 +- ebook/20_ES2018_async_iteration_and_more.md | 2 +- ...d => 21_ES2019_string_methods_and_more.md} | 4 +- ...hat_is_coming.md => 22_what_new_es2020.md} | 135 +++--- ebook/24_what_new_es2022.md | 384 ++++++++++++++++++ ebook/{24_conclusion.md => 25_conclusion.md} | 0 9 files changed, 489 insertions(+), 81 deletions(-) rename ebook/{21_ES2019_what_is_coming.md => 21_ES2019_string_methods_and_more.md} (98%) rename ebook/{22_es2020_what_is_coming.md => 22_what_new_es2020.md} (80%) create mode 100644 ebook/24_what_new_es2022.md rename ebook/{24_conclusion.md => 25_conclusion.md} (100%) diff --git a/README.md b/README.md index 1681413..c051711 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ # [The Complete Guide to Modern JavaScript ](https://leanpub.com/completeguidetomodernjavascript2020) -## Learn everything from the basics of JavaScript to the new ES2021 features. Practice with more than 50 quizzes and dive into the basics of TypeScript. +## Learn everything from the basics of JavaScript to the new ES2022 features. Practice with more than 50 quizzes and dive into the basics of TypeScript. [![book-cover](/assets/banner.jpg)](http://bit.ly/2VV2LbX) @@ -22,9 +22,9 @@ This book is **not** for total beginners, it does **cover** the basics of progra   -## 4th Edition is out +## 5th Edition is out -Included a new chapter detailing all the new features introduced by ES2021 +Included a new chapter detailing all the new features introduced by ES2022 ## Free vs Paid Version @@ -44,13 +44,13 @@ The Paid version gives you access to: You can get the course based on this book on: - [Leanpub](https://leanpub.com/c/completeguidetomodernjavascript) -- [Educative](https://www.educative.io/courses/complete-guide-to-modern-javascript?aff=BqmB) +- [Educative](https://www.educative.io/courses/complete-guide-to-modern-javascript?aff=BqmB) or get the complete path to become a front end develop [here](https://www.educative.io/path/become-front-end-developer?aff=BqmB) You get the ebook on Amazon, Leanpub and other stores, check the following link to find your local amazon store: - Play Store [Ebook](https://play.google.com/store/books/details/Alberto_Montalesi_The_Complete_Guide_to_Modern_Jav?id=avqrDwAAQBAJ) - Leanpub: [Ebook](https://leanpub.com/completeguidetomodernjavascript2020) -- Amazon Global: [Paperback](https://bit.ly/2QTvHzn) +- Amazon: [Paperback](https://www.amazon.com/dp/B09FNNVY1Y?ref=inspiredwebde-20) If you enjoyed the book please leave a review to support me and help other buyers. diff --git a/ebook/03_default_function_arguments.md b/ebook/03_default_function_arguments.md index 13d294f..c05f834 100644 --- a/ebook/03_default_function_arguments.md +++ b/ebook/03_default_function_arguments.md @@ -112,6 +112,13 @@ Cannot destructure property `total` of 'undefined' or 'null'. By writing `= {}` we default our argument to an `Object` and no matter what argument we pass in the function, it will be an `Object`: ```javascript +function calculatePrice({ + total = 0, + tax = 0.1, + tip = 0.05} = {}){ + return total + (total * tax) + (total * tip); +} + calculatePrice({}); // 0 calculatePrice(); diff --git a/ebook/10_object_literal_upgrades.md b/ebook/10_object_literal_upgrades.md index fae0a5a..f78ac39 100644 --- a/ebook/10_object_literal_upgrades.md +++ b/ebook/10_object_literal_upgrades.md @@ -108,7 +108,7 @@ var name = "myname"; var person = {}; // update the object person[name] = "Alberto"; -console.log(person.name); +console.log(person.myname); // Alberto ``` diff --git a/ebook/11_symbols.md b/ebook/11_symbols.md index 7ce62b6..db00d1b 100644 --- a/ebook/11_symbols.md +++ b/ebook/11_symbols.md @@ -43,12 +43,12 @@ As we mentioned earlier, we can use them to create identifiers for object proper ```javascript const office = { - "Tom" : "CEO", - "Mark": "CTO", - "Mark": "CIO", -} + Tom: "CEO", + Mark: "CTO", + Mark: "CIO", +}; -for (person in office){ +for (person in office) { console.log(person); } // Tom @@ -60,12 +60,12 @@ To avoid naming collisions we can use symbols. ```javascript const office = { - [Symbol("Tom")] : "CEO", - [Symbol("Mark")] : "CTO", - [Symbol("Mark")] : "CIO", -} + [Symbol("Tom")]: "CEO", + [Symbol("Mark")]: "CTO", + [Symbol("Mark")]: "CIO", +}; -for(person in office) { +for (person in office) { console.log(person); } // undefined @@ -93,6 +93,12 @@ console.log(symbols); We retrieved the array, but to be able to access the properties we have to use `map`. ```javascript +const office = { + [Symbol("Tom")] : "CEO", + [Symbol("Mark")] : "CTO", + [Symbol("Mark")] : "CIO", +}; + const symbols = Object.getOwnPropertySymbols(office); const value = symbols.map(symbol => office[symbol]); console.log(value); diff --git a/ebook/20_ES2018_async_iteration_and_more.md b/ebook/20_ES2018_async_iteration_and_more.md index b500e55..5ea3c33 100644 --- a/ebook/20_ES2018_async_iteration_and_more.md +++ b/ebook/20_ES2018_async_iteration_and_more.md @@ -1,4 +1,4 @@ -# Chapter 20: ES2018 Async Iteration and more? +# Chapter 20: ES2018 Async Iteration and more In this chapter we will look at what was introduced with ES2018. diff --git a/ebook/21_ES2019_what_is_coming.md b/ebook/21_ES2019_string_methods_and_more.md similarity index 98% rename from ebook/21_ES2019_what_is_coming.md rename to ebook/21_ES2019_string_methods_and_more.md index 6480be0..620ba81 100644 --- a/ebook/21_ES2019_what_is_coming.md +++ b/ebook/21_ES2019_string_methods_and_more.md @@ -1,8 +1,8 @@ -# Chapter 21: What's new in ES2019? +# Chapter 21: What's new in ES2019 In this chapter we will look at what is included in the latest version of `ECMAScript`: ES2019. -  +  ## `Array.prototype.flat()` / `Array.prototype.flatMap()` diff --git a/ebook/22_es2020_what_is_coming.md b/ebook/22_what_new_es2020.md similarity index 80% rename from ebook/22_es2020_what_is_coming.md rename to ebook/22_what_new_es2020.md index c40f0a8..a8239c3 100644 --- a/ebook/22_es2020_what_is_coming.md +++ b/ebook/22_what_new_es2020.md @@ -1,6 +1,6 @@ -# Chapter 22: What's coming in ES2020 +# Chapter 22: What's new in ES2020 -The latest version of ECMAScript, ES2020, includes many new interesting changes and we are going to cover them in this chapter. +`ES2020` includes many new interesting changes and we are going to cover them in this chapter. Not all browsers currently support these features so, I recommend you use the latest version of Chrome or Firefox to test the code examples. Otherwise, if you want to use them in your project, be sure to install a compiler like **Babel**, which at their latest version 7.8 already supports ES2020 by default so you don't need to use any plugin. @@ -9,7 +9,7 @@ Not all browsers currently support these features so, I recommend you use the la The support for `BigInt` means that we will be able store much larger integers in our `JavaScript`. The current max is 2^53 and you can get it by using `Number.MAX_SAFE_INTEGER`. That does not mean that you cannot store larger integer, but `JavaScript` does not handle them well, let's look at an example: ```javascript -let num = Number.MAX_SAFE_INTEGER +let num = Number.MAX_SAFE_INTEGER; // 9007199254740991 num + 1; // 9007199254740992 @@ -41,12 +41,10 @@ As you can see I did not add `1` but I added `1n`, that's because you can't add This will allow you to dynamically import your modules when you need them. Look at the following example: ```javascript -if(condition1 && condition2){ - const module = await import('./path/to/module.js'); - module.doSomething(); +if (condition1 && condition2) { + const module = await import("./path/to/module.js"); + module.doSomething(); } - - ``` If you don't need a module, you don't have to import it and you can just do that when/if it's needed, using `async/await`. @@ -59,19 +57,18 @@ Let's take these simple `Object` that represent our Users. ```js const user1 = { - name: 'Alberto', - age: 27, - work: { - title: 'software developer', - location: 'Vietnam' - } -} + name: "Alberto", + age: 27, + work: { + title: "software developer", + location: "Vietnam", + }, +}; const user2 = { - name: 'Tom', - age: 27 -} - + name: "Tom", + age: 27, +}; ``` Let's say we want to display the job title of our user. @@ -79,15 +76,15 @@ As we can see, `work` is an optional property of our `Object` so we would have t ```js let jobTitle; -if (user.work){ - jobTitle = user.work.title +if (user.work) { + jobTitle = user.work.title; } ``` or using a ternary operator: ```js -const jobTitle = user.work ? user.work.title : '' +const jobTitle = user.work ? user.work.title : ""; ``` Before we access the property `title` of `work` we need to check that the user actually has a `work`. @@ -97,7 +94,7 @@ When we are dealing with simple objects it's not such a big deal but when the da This is where the Optional Chaining `?.` operator comes to the rescue. This is how we would rewrite our code with this new operator: ```js -const jobTitle = user.work?.title +const jobTitle = user.work?.title; ``` Done! More concise and readable. @@ -117,31 +114,43 @@ Imagine dealing with a deeply nested object with optional properties such as the ```js const elon = { - name: 'Elon Musk', - education: { - primary_school: { /* primary school stuff */ }, - middle_school: { /* middle school stuff */ }, - high_school: {/* high school stuff here */}, - university: { - name: 'University of Pennsylvania', - graduation: { - year: 1995 - } - } - } -} + name: "Elon Musk", + education: { + primary_school: { + /* primary school stuff */ + }, + middle_school: { + /* middle school stuff */ + }, + high_school: { + /* high school stuff here */ + }, + university: { + name: "University of Pennsylvania", + graduation: { + year: 1995, + }, + }, + }, +}; const mark = { - name: 'Mark Zuckerberg', - education: { - primary_school: { /* primary school stuff */ }, - middle_school: { /* middle school stuff */ }, - high_school: {/* high school stuff here */}, - university: { - name: 'Harvard University', - } - } -} + name: "Mark Zuckerberg", + education: { + primary_school: { + /* primary school stuff */ + }, + middle_school: { + /* middle school stuff */ + }, + high_school: { + /* high school stuff here */ + }, + university: { + name: "Harvard University", + }, + }, +}; ``` Not all of our Users have studied in University so that property is going to be optional and the same goes for the graduation as some have dropped out and didn't finish the study. @@ -150,8 +159,11 @@ Now imagine wanting to access the graduation year of our two users: ```js let graduationYear; -if( - user.education.university && user.education.university.graduation && user.education.university.graduation.year){ +if ( + user.education.university && + user.education.university.graduation && + user.education.university.graduation.year +) { graduationYear = user.education.university.graduation.year; } ``` @@ -174,14 +186,13 @@ ES6 added `Promise.all` that let us await until all the promises given to it are This means that we will be able to tell easily which one of our promises is failing: ```javascript - const arrayOfPromises = [ - new Promise((res, rej) => setTimeout(res, 1000)), - new Promise((res, rej) => setTimeout(rej, 1000)), - new Promise((res, rej) => setTimeout(res, 1000)), -] + new Promise((res, rej) => setTimeout(res, 1000)), + new Promise((res, rej) => setTimeout(rej, 1000)), + new Promise((res, rej) => setTimeout(res, 1000)), +]; -Promise.allSettled(arrayOfPromises).then(data => console.log(data)); +Promise.allSettled(arrayOfPromises).then((data) => console.log(data)); // [ // Object { status: "fulfilled", value: undefined}, @@ -221,10 +232,10 @@ As you can see, all of these values are falsey. Sometimes we want to distinguish The Nullish Coalescing operator (`??`) returns the right-hand side operand when the left-hand side is nullish. ```javascript -const x = '' ?? 'empty string'; +const x = "" ?? "empty string"; console.log(x); // '' -const num = 0 ?? 'zero'; +const num = 0 ?? "zero"; console.log(num); // 0 const n = null ?? "it's null"; @@ -246,7 +257,7 @@ The `matchAll()` method is a new string method that returns an iterator of all t ```javascript // regex that matches any character in the range from 'a' to 'd' const regEx = /[a-d]/g; -const str = "Lorem ipsum dolor sit amet" +const str = "Lorem ipsum dolor sit amet"; const regExIterator = str.matchAll(regEx); console.log(Array.from(regExIterator)); @@ -265,19 +276,19 @@ As you can see, we called the `matchAll` method against our string and since our We could already do something like this: ```javascript -import * as stuff from './test.mjs'; +import * as stuff from "./test.mjs"; ``` But now we can also do the same for **exports**: ```javascript -export * as stuff from './test.mjs'; +export * as stuff from "./test.mjs"; ``` which would be the same as doing: ```javascript -export { stuff } +export { stuff }; ``` It's not a game-changer feature, but it adds a better symmetry between import and export statements and their syntax. @@ -289,7 +300,7 @@ It's not a game-changer feature, but it adds a better symmetry between import an The `import.meta` object exposes information about a module, such as its URL. ```javascript - +; console.log(import.meta); // { url: "file:///home/user/test.js" } ``` @@ -305,4 +316,4 @@ You would have to manually detect the environment at runtime and bind the approp Now, in ES202 you can use the `globalThis` which always refers to the `global` object. In Browsers, due to the fact, the `global` object is not directly accessible, the `globalThis` will be a reference to a `Proxy` of it. -Using the new `globalThis` you won't have to worry anymore about the environment in which your application is running in order to access this global value. \ No newline at end of file +Using the new `globalThis` you won't have to worry anymore about the environment in which your application is running in order to access this global value. diff --git a/ebook/24_what_new_es2022.md b/ebook/24_what_new_es2022.md new file mode 100644 index 0000000..7503704 --- /dev/null +++ b/ebook/24_what_new_es2022.md @@ -0,0 +1,384 @@ +# Chapter 24: What's new in ES2022 + +Let's get started with the first of the new ES2022 features: + +## Class Fields + +### Class public Instance Fields & private Instance Fields + +Before ES2022 we would define properties of a `class` in its `constructor` like this: + +```js +class ButtonToggle extends HTMLElement { + constructor(){ + super(); + // public field + this.color = 'green' + // private field + this._value = true; + } + + toggle(){ + this.value = !this.value + } +} + +const button = new ButtonToggle(); +console.log(button.color); +// green - public fields are accessible from outside classes + +button._value = false; +console.log(button._value); +// false - no error thrown, we can access it from outside the class +``` + +Inside of the `constructor`, we defined two fields. As you can see one of them is marked with an `_` in front of the name which is just a `JavaScript` naming convention to declare the field as `private` meaning that it can only be accessed from inside of a `class` method. Of course, that's just a naming convention and not something that the language itself enforces and that's why when we tried to access it, it didn't raise any error. + +In ES2022 we have an easier way to declare both `public` and `private` fields. Let's have a look at this updated example: + +```js +class ButtonToggle extends HTMLElement { + + color = 'green'; + #value = true; + + toggle(){ + this.#value = !this.#value; + } +} +const button = new ButtonToggle(); +console.log(button.color); +// green - public fields are accessible from outside classes + +// SyntaxError - cannot be accessed or modified from outside the class +console.log(button.#value); +button.#value = false; +``` + +The first thing to notice is that don't have to define them inside of the `constructor`. Secondly, we can also define `private` fields by pre-pending `#` to their names. + +The main difference with the previous example is that this time an actual error will be thrown if we try to access or modify the field outside of the class. + +  + +### Private methods and getter/setters for JavaScript classes + +Similar to how we did in the previous example, we can also define `private` methods and getter/setters for our classes. + +```js +class ButtonToggle extends HTMLElement { + + color = 'green' + #value = true; + + #toggle(){ + this.#value = !this.#value + } + + set #setFalseValue(){ + this.#value = false; + } +} +const button = new ButtonToggle(); +// SyntaxError - cannot be accessed or modified from outside the class +button.#toggle(); +// SyntaxError - cannot be accessed or modified from outside the class +button.#setFalseValue; +``` + +In the example above we replaced `toggle()` with `#toggle()` thus making the `toggle` method `private` and only accessible from inside of the `class`. + +### Static class fields and private static methods + +A `static` field or method is only accessible in the prototype and not in every instance of a `class` and ES2022 provides us with the means to define `static` fields and `static` public/private methods by using the `static` keyword. + +Previously we would have to define them outside of the `class` body such as: + +```js +class ButtonToggle extends HTMLElement { + // ... class body +} +ButtonToggle.toggle(){ + // static method define outside of the class body +} +``` + +Now, instead, we can define them directly inside of the `class` body with the use of the `static` keyword: + +```js +class ButtonToggle extends HTMLElement { + + #value = true; + + static toggle(){ + this.#value = !this.#value + } +} +// this will work +ButtonToggle.toggle(); + +// SyntaxError - private static field +const button = new ButtonToggle(); +button.toggle(); +``` + +As you can see in the example above, we can access `toggle()` directly on our `ButtonToggle` but we cannot do the same on a new instance of it. + +We can use the `static` keyword in front of fields and methods (both private and public) and by combining it with the `#` (`private`) we can create a `private static` method only accessible from inside of our prototype `class`. + +```js +class ButtonToggle extends HTMLElement { + + #value = true; + + static #toggle(){ + this.#value = !this.#value + } +} +// this will error, it's a private static method +ButtonToggle.#toggle(); +``` + +  + +## Ergonomic brand checks for private Fields + +As we saw in the examples above, if we try to access a `private` field outside of a `class` it will throw an exception and will not return `undefined` like it does with `public` fields. + +We could try using a simple `try/catch` inside of the `class` to check if the field exists: + +```js +class ButtonToggle extends HTMLElement { + + // initialised as null + #value = null; + + get #getValue(){ + if(!this.#value){ + throw new Error('no value'); + } + return this.#value + } + + static isButtonToggle(obj){ + try { + obj.#getValue; + return true; + } catch { + // could be an error internal to the getter + return false; + } + } + +} +``` + +In the example above we added a `private` `getter` that will throw an error if there is no value yet. We then created a `static` method to access that `getter` and tried to determine if it exists by checking with a `try/catch`. The problem lies in the fact that we don't know if the code in the `catch` is executed because the `getter` is not present or simply because it threw an error. + +ES2022 provides us with an easy way to check if said field belongs to a `class` by using the operator `in`. Let's rework our example code: + +```js +class ButtonToggle extends HTMLElement { + + // initialised as null + value = null; + + get #getValue(){ + if(!this.#value){ + throw new Error('no value'); + } + return this.#value; + } + + static isButtonToggle(obj){ + return #value in obj && #getValue in obj + } + +} +``` + +Our method `isButtonToggle` will check if the `class` contains the `private` fields 'value' and 'getValue'. + +  + +## Class Static Block + +This is yet another upgrade to the `static` fields in ES2022 that allows us to have `static` blocks inside of classes. The issue this is trying to solve arises from the fact that we cannot evaluate statements such as a `try/catch` during initialization meaning that we would have to put that code **outside** of the `class` body: + +```js +class ButtonToggle{ + value = false; + + get getValue(){ + if(!this.#value){ + throw new Error('no value'); + } + return this.#value + } +} + +// this has to sit outside of the class body +try { + const val = ButtonToggle.getValue; + ButtonToggle.value = val +} catch { + ButtonToggle.value = false +} +``` + +As you can see, our `try/catch` had to be put outside of the `class` body. Thankfully we can replace that with a `static` block like the following: + +```js +// method defined outside of the class body +let initVal; + +class ButtonToggle{ + #value = false; + + get getValue(){ + if(!this.#value){ + throw new Error('no value'); + } + return this.#value + } + + static { + initVal = () => { + this.#value = this.getValue; + } + } +} + +initVal(); +``` + +We created a `static` block inside of our `class` that defines a function that we declared outside of the context of that `class`. As you can see, the method will have access to '#value' which is a `private` field or our class. They will have access to `private` methods and fields, being them `instance-private` (meaning non `static`, `private` fields) or `static-private`. + +## RegExp Match Indices + +This upgrade will allow us to use the `d` character to specify that we want to get the indices (starting and ending) of the matches of our RegExp. + +We can use `Regexp.exec` or `String.matchAll` to find a list of matches, with the main difference between them being that `Regexp.exec` returns its results one by one whereas `String.matchAll` returns an iterator. Let's see them in practice: + +```js +const fruits = 'Fruits: mango, mangosteen, orange' +const regex = /(mango)/g; + +// .exec +RegExp(regex).exec(fruits); +// [ +// 'mango', +// index: 8, +// input: 'Fruits: mango, mangosteen, orange', +// groups: undefined +// ] + +// matchAll +const matches = [...fruits.matchAll(regex)]; +matches[0]; +// [ +// 'mango', +// 'mango', +// index: 8, +// input: 'Fruits: mango, mangosteen, orange', +// groups: undefined +// ] +``` + +Both return the index of the match, the match itself, and the initial input. What we don't know are the indices at which the string ends, something that we will now be able to do like this: + +```js +const fruits = 'Fruits: mango, mangosteen, orange' +// /gd instead of the previous /g +const regex = /(mango)/gd; + +const matches = [...fruits.matchAll(regex)]; +matches[0]; + +// [ +// "mango", +// "mango", +// groups: undefined +// index: 8 +// indices:[] +// [8, 13], +// [8, 13] +// ] +// groups: undefined +``` + +As you can see it returned [8,13] as the indices of the first occurrence of 'mango' in our string.] + +  +## Top-level await + +"`await` operator can only be used within an `async` method" is probably an error you have encountered frequently. In ES2022 we will be able to use it outside of the context of an `async` method in our modules. For example, we could defer the execution of a module and its parent until something else is imported. + +This can be useful in many scenarios, for example when we have a **dynamic path** for a dependency that depends on a runtime value: + +```js +// we need to get the appropriate translation keys based on the language +const translationKeys = await import(`/i18n/${navigator.language}`); +``` + +Another use could be to provide a fallback for a dependency: + +```js +let jQuery; +try { + jQuery = await import('https://cdn-a.com/jQuery'); +} catch { + jQuery = await import('https://cdn-b.com/jQuery'); +} +``` + +## .at() + +In `JavaScript` you can do `arr[1]` to access the value at index 1 of an `Array` but you cannot do `arr[-1]` to count backward from the ending of the `Array`. The reason is that the brackets syntax is used not only for arrays but also for Objects, where `obj[-1]` would simply refer to the property '-1' of that `Object`. + +With the .`at()` method we now have an easy way to access any index, positive or negative of arrays and strings: + +```js +const arr = [10,20,30,40]; + +// same -> 10 +arr[1]; +arr.at(1); + +// same -> 40 +arr[arr.length -1]; +arr.at(-1); +``` + +Note that a negative value simply means: 'Start counting backward from the end of the array'. +  + +## Accessible Object.prototype.hasOwnProperty + +In `JavaScript` we already have an `Object.prototype.hasOwnProperty` but, as the MDN documentation also suggests, it's best to not use `hasOwnProperty` outside the prototype itself as it is not a protected property, meaning that an `object` could have its property called `hasOwnProperty` that has nothing to do with `Object.prototype.hasOwnProperty`. + +For example: + +```js +const obj = { + hasOwnProperty:()=> { + return false + } +} + +obj.hasOwnProperty('prop'); // false +``` + +As you can see, we defined our own method `hasOwnProperty` that has overridden the one on the prototype, an issue that is not present with `Object.hasOwn()`. + +`Object.hasOwn()` takes our `Object` as the first argument and the property we want to check as the second: + +```js +const student = { + name: 'Mark', + age: 18 +} + +Object.hasOwn(student,'age'); // true +Object.hasOwn(student,'grade'); // false +``` \ No newline at end of file diff --git a/ebook/24_conclusion.md b/ebook/25_conclusion.md similarity index 100% rename from ebook/24_conclusion.md rename to ebook/25_conclusion.md