Click ⭐if you like the project. Pull Request are highly appreciated. Follow me @SudheerJonna for technical updates.
You can download the PDF and Epub version of this repository from the latest run on the actions tab.
npm install npx babel-node es2020/bigint // Try other examples tooECMAScript is the scripting language which acts as the basis of JavaScript. ECMAScript standardized by the ECMA International standards organization in the ECMA-262 and ECMA-402 specifications. Each proposal for an ECMAScript feature goes through the following maturity stages:
- Stage 0: Strawman;
- Stage 1: Proposal;
- Stage 2: Draft;
- Stage 3: Candidate;
- Stage 4: Finished.
The variable scoping determines the visibility or accessibility of a variable within the certain part of the program or region. In ES6, both const and let keywords allow developers to declare variables in the block scope. The let statement declares a block-scoped local variable
The arrow functions provides a more concise syntax for writing function expressions by opting out the function and return keywords using fat arrow(=>) notation. Let's see how this arrow function looks like,
// Function Expression var multiplyFunc = function(a, b) { return a * b; } console.log(multiplyFunc(2, 5)); // 10 // Arrow function var multiplyArrowFunc = (a, b) => a * b; console.log(multiplyArrowFunc(2, 5)); // 10You can also skip parenthesis(()) if the function has exactly one parameter(either zero or more than one parameter). Apart from this, you can wrap braces({}) if the function has more than one expression in the body.
Let's list down all the variations of arrow functions,
//1. Single parameter and single statement var message = name => console.log("Hello, " + name + "!"); message("Sudheer"); // Hello, Sudheer! //2. Multiple parameters and single statement var multiply = (x, y) => x * y; console.log(multiply(2, 5)); // 10 //3. Single parameter and multiple statements var even = number => { if(number % 2) { console.log("Even"); } else { console.log("Odd"); } } even(5); // odd //4. Multiple parameters and multiple statements var divide = (x, y) => { if(y != 0) { return x / y; } } console.log(divide(100, 5)); // 20 //5. No parameter and single statement var greet = () => console.log('Hello World!'); greet(); // Hello World!The classes are introduced as syntactic sugar over existing prototype based inheritance and constructor functions. So this feature doesn't bring new object-oriented inheritance model to JavaScript.
Set is a built-in object to store collections of unique values of any type.
let mySet = new Set() mySet.add(1); mySet.add(2); mySet.add(2); mySet.add('some text here'); mySet.add({one: 1, two: 2 , three: 3}); console.log(mySet); // Set [ 1, 2, 'some text here', {one: 1, two: 2 , three: 3} ] console.log(mySet.size) // 4 console.log(mySet.has(2)); // trueThe Set is used to store any type of data such as primitives and object types. Whereas WeakSet is an object to store weakly held objects in a collection. (i.e, WeakSet is the collections of objects only). Here weak means, If no other references to an object stored in the WeakSet exist, those objects can be garbage collected.
let myUserSet = new WeakSet(); let john = { name: "John" }; let rocky = { name: "Rocky" }; let alex = { name: "Alex" }; let nick = { name: "Nick" }; myUserSet.add(john); myUserSet.add(rocky); myUserSet.add(john); myUserSet.add(nick); console.log(myUserSet.has(john)); // true console.log(myUserSet.has(alex)); // false console.log(myUserSet.delete(nick)); console.log(myUserSet.has(nick)); // false john = null;Map is a collection of elements where each element is stored as a Key, value pair. It can hold both objects and primitive values as either key or value and iterates its elements in insertion order.
Let's take a map with different types of primitives and objects as key-value pairs and various methods on it,
let typeMap = new Map(); var keyObj = {'one': 1} typeMap.set('10', 'string'); // a string key typeMap.set(10, 'number'); // a numeric key typeMap.set(true, 'boolean'); // a boolean key typeMap.set(keyObj, 'object'); // an object key console.log(typeMap.get(10) ); // number console.log(typeMap.get('10') ); // string console.log(typeMap.get(keyObj)) // object console.log(typeMap.get({'one': 1})) // undefined console.log(typeMap.size ); // 3 for(let item of typeMap) { console.log(item); } for(let item in typeMap) { console.log(item); }WeakMap object is a collection of key/value pairs in which the keys are weakly referenced. For this object, the keys must be objects and the values can be arbitrary values.
Let's see various methods of weakmap with below example,
var weakMap = new WeakMap(); var obj1 = {} var obj2 = {} weakMap.set(obj1, 1); weakMap.set(obj2, 2); weakMap.set({}, {"four": 4}); console.log(weakMap.get(obj2)); // 2 console.log(weakMap.has({})); // return false even though empty object exists as key. Because the keys have different references delete obj2; console.log(weakMap.get(obj2)); // 2 weakMap.delete(obj1) console.log(weakMap.get(obj1)); //undefinedPrior to ES6, JavaScript strings are represented by 16-bit character encoding (UTF-16). Each character is represented by 16-bit sequence known as code unit. Since the character set is been expanded by Unicode, you will get unexpected results from UTF-16 encoded strings containing surrogate pairs(i.e, Since it is not sufficient to represent certain characters in just 16-bits, you need two 16-bit code units).
let str = '𠮷'; console.log(str.length); // 2 console.log(text.charAt(0)); // "" console.log(text.charAt(1)); // "" console.log(text.charCodeAt(0)); // 55362(1st code unit) console.log(text.charCodeAt(1)); // 57271(2nd code unit) console.log(/^.$/.test(str)); // false, because length is 2 console.log('\u20BB7); // 7!(wrong value) console.log(str === '\uD842\uDFB7'); // trueECMAScript 6 added full support for UTF-16 within strings and regular expressions. It introduces new Unicode literal form in strings and new RegExp u mode to handle code points, as well as new APIs(codePointAt, fromCodePoint) to process strings.
let str = '𠮷'; // new string form console.log('\u{20BB7}'); // "𠮷" // new RegExp u mode console.log(new RegExp('\u{20BB7}', 'u')); console.log(/^.$/u.test(str)); // true //API methods console.log(str.codePointAt(0)); // 134071 console.log(str.codePointAt(1)); // 57271 console.log(String.fromCodePoint(134071)); // "𠮷"Symbol is a new peculiar primitive data type of JavaScript, along with other primitive types such as string, number, boolean, null and undefined. The new symbol is created just by calling the Symbol function. i.e, Every time you call the Symbol function, you’ll get a new and completely unique value. You can also pass a parameter to Symbol(), which is useful for debugging purpose only.
Even though equality checks on two symbols is always false, it will be true while comparing symbols with .for method due to global registry (i.e, Symbols.for('key') === Symbols.for('key'))
These symbols are useful to uniquely identify properties or unique constants,
//1. Object properties let id = Symbol("id"); let user = { name: "John", age: 40, [id]: 111 }; for (let key in user) { console.log(key); // name, age without symbols } console.log(JSON.stringify(user)); // {"name":"John", "age": 40} console.log(Object.keys(user)); // ["name", "age"] console.log( "User Id: " + user[id] ); // Direct access by the symbol works //2. Unique constants const logLevels = { DEBUG: Symbol('debug'), INFO: Symbol('info'), WARN: Symbol('warn'), ERROR: Symbol('error'), }; console.log(logLevels.DEBUG, 'debug message'); console.log(logLevels.INFO, 'info message'); //3. Equality Checks console.log(Symbol('foo') === Symbol('foo')); // false console.log(Symbol.for('foo') === Symbol.for('foo')); // trueReflection is the ability of a code to inspect and manipulate variables, properties, and methods of objects at runtime. JavaScript already provides Object.keys(), Object.getOwnPropertyDescriptor(), and Array.isArray() methods as classic refection features. In ES6, it has been officially provided through Reflect object. Reflect is a new global object which is used to call methods, construct objects, get and set properties, manipulate and extend properties. It is similar to Math and JSON objects and all the methods of this object are static.
Proper tail call(PTC) is a technique where the program or code will not create additional stack frames for a recursion when the function call is a tail call.
For example, the below classic or head recursion of factorial function relies on stack for each step. Each step need to be processed upto n * factorial(n - 1)
function factorial(n) { if (n === 0) { return 1 } return n * factorial(n - 1) } console.log(factorial(5)); //120But if you use Tail recursion functions, they keep passing all the necessary data it needs down the recursion without relying on the stack.
function factorial(n, acc = 1) { if (n === 0) { return acc } return factorial(n - 1, n * acc) } console.log(factorial(5)); //120The above pattern returns the same output as first one. But the accumulator keeps track of total as an argument without using stack memory on recursive calls.
The browsers which supports PTC do not generate stack overflow instead shows Infinity with below inputs,
console.log(factorial(10)); console.log(factorial(100)); console.log(factorial(1000)); console.log(factorial(10000));ES2015/ES6 introduced a huge set of new features. But ECMAScript 2016 Or ES7 introduced only two new features:
- Array.prototype.includes()
- Exponentiation operator
Prior to ES7, you have to use indexOf method and compare the result with '-1' to check whether an array element contains particular element or not.
const array = [1,2,3,4,5,6]; if(array.indexOf(5) > -1 ){ console.log("Found an element"); }Whereas in ES7, array.prototype.includes() method is introduced as a direct approach to determine whether an array includes a certain value among its entries or not.
const array = [1,2,3,4,5,6]; if(array.includes(5)){ console.log("Found an element"); }In addition to this, Array.prototype.includes() handles NaN and Undefined values better than Array.prototype.indexOf() methods. i.e, If the array contains NaN and Undefined values then indexOf() does not return correct index while searching for NaN and Undefined.
let numbers = [1, 2, 3, 4, NaN, ,]; console.log(numbers.indexOf(NaN)); // -1 console.log(numbers.indexOf(undefined)); // -1On the otherhand, includes method is able to find these elements
let numbers = [1, 2, 3, 4, NaN, ,]; console.log(numbers.includes(NaN)); // true console.log(numbers.includes(undefined)); // trueThe older versions of javascript uses Math.pow function to find the exponentiation of given numbers. ECMAScript 2016 introduced the exponentiation operator, **(similar to other languages such as Python or F#) to calculate the power computation in a clear representation using infix notation.
//Prior ES7 const cube = x => Math.pow(x, 3); console.log(cube(3)); // 27 //Using ES7 const cube1 = x => x ** 3; console.log(cube1(3)); // 27In ES6, Promises were introduced to solve the famous callback hell problem. When a series of nested asynchronous functions need to be executed in order, it leads to a callback hell
function task() { task1((response1) => { task2(response1, (response2) => { task3(response2, (response3) => { // etc... }; }); }); }But the Chained Promises creates complex flow for asynchronous code.
Async functions were introduced as a combination of promises and generators to give us the possibility of writing asynchronous in a synchronous manner. i.e, This function is going to be declared with the async keyword which enable asynchronous, promise-based behavior to be written in a cleaner style by avoiding promise chains. These functions can contain zero or more await expressions.
Let's take a below async function example,
```js async function logger() { let data = await fetch('http://someapi.com/users'); // pause until fetch returns console.log(data) } logger(); ``` Similar to Object.keys which iterate over JavaScript object’s keys, Object.values will do the same thing on values. i.e, The Object.values() method is introduced to returns an array of a given object's own enumerable property values in the same order as for...in loop.
const countries = { IN: 'India', SG: 'Singapore', } Object.values(countries) // ['India', 'Singapore']By the way, non-object argument will be coerced to an object
console.log(Object.values(['India', 'Singapore'])); // ['India', 'Singapore'] console.log(Object.values('India')); // ['I', 'n', 'd', 'i', 'a']The Object.entries() method is introduced to returns an array of a given object's own enumerable string-keyed property [key, value] pairsin the same order as for...in loop.
const countries = { IN: 'India', SG: 'Singapore', } Object.entries(countries) // [["IN", "India"], ["SG", "Singapore"]]By the way, non-object argument will be coerced to an object
const countries = ['India', 'Singapore']; console.log(Object.entries(countries)); // [ ['0', 'India'], ['1', 'Singapore']] const country = 'India'; console.log(Object.entries(country)); // [["0", "I"], ["1", "n"], ["2", "d"], ["3", "i"], ["4", "a"]] console.log(Object.entries(100)); // [], an empty array for any primitive type because it won't have any own propertiesProperty descriptors describe the attributes of a property. The Object.getOwnPropertyDescriptors() method returns all own property descriptors of a given object.
It provides the below attributes,
- value: The value associated with the property (data descriptors only).
- writable: true if and only if the value associated with the property may be changed
- get: A function which serves as a getter for the property.
- set: A function which serves as a setter for the property.
- configurable: true if and only if the type of this property descriptor may be changed or deleted.
- enumerable: true if and only if this property shows up during enumeration of the property.
The usage of finding property descriptors for any property seems to be as below,
const profile = { age: 42 }; const descriptors = Object.getOwnPropertyDescriptors(profile); console.log(descriptors); // {age: {configurable: true, enumerable: true, writable: true }}Some strings and numbers(money, date, timers etc) need to be represented in a particular format. Both padStart() & padEnd() methods introduced to pad a string with another string until the resulting string reaches the supplied length.
- padStart(): Using this method, padding is applied to the left or beginning side of the string.
For example, you may want to show only the last four digits of credit card number for security reasons, ```js const cardNumber = '01234567891234'; const lastFourDigits = cardNumber.slice(-4); const maskedCardNumber = lastFourDigits.padStart(cardNumber.length, '*'); console.log(maskedCardNumber); // expected output: "**********1234" ``` - padEnd(): Using this method, padding is applied to the right or ending side of the string.
For example, the profile information padded for label and values as below
const label1 = "Name"; const label2 = "Phone Number"; const value1 = "John" const value2 = "(222)-333-3456"; console.log((label1 + ': ').padEnd(20, ' ') + value1); console.log(label2 + ": " + value2); // Name: John // Phone Number: (222)-333-3456The Atomics is a global object which provides atomic operations to be performed as static methods. They are used with SharedArrayBuffer(fixed-length binary data buffer) objects. The main use cases of these methods are,
-
atomic operations: When memory is shared, multiple threads can read and write the same data in memory. So there would be a chance of loss of data. But atomic operations make sure that predictable values are written and read, that operations are finished before the next operation starts and that operations are not interrupted.
It provides static methods such as add, or, and, xor, load, store, isLockFree etc as demonstrated below.
const sharedMemory = new SharedArrayBuffer(1024); const sharedArray = new Uint8Array(sharedMemory); sharedArray[0] = 10; Atomics.add(sharedArray, 0, 20); console.log(Atomics.load(sharedArray, 0)); // 30 Atomics.sub(sharedArray, 0, 10); console.log(Atomics.load(sharedArray, 0)); // 20 Atomics.and(sharedArray, 0, 5); console.log(Atomics.load(sharedArray, 0)); // 4 Atomics.or(sharedArray, 0, 1); console.log(Atomics.load(sharedArray, 0)); // 5 Atomics.xor(sharedArray, 0, 1); console.log(Atomics.load(sharedArray, 0)); // 4 Atomics.store(sharedArray, 0, 10); // 10 Atomics.compareExchange(sharedArray, 0, 5, 10); console.log(Atomics.load(sharedArray, 0)); // 10 Atomics.exchange(sharedArray, 0, 10); console.log(Atomics.load(sharedArray, 0)); //10 Atomics.isLockFree(1); // true
-
waiting to be notified: Both
wait()andnotify()methods provides ways for waiting until a certain condition becomes true and are typically used as blocking constructs.Let's demonstrate this functionality with reading and writing threads.
First define a shared memory and array
const sharedMemory = new SharedArrayBuffer(1024); const sharedArray = new Int32Array(sharedMemory);
A reading thread is sleeping and waiting on location 0 which is expected to be 10. You can observe a different value after the value overwritten by a writing thread.
Atomics.wait(sharedArray, 0, 10); console.log(sharedArray[0]); // 100
Now a writing thread stores a new value(e.g, 100) and notifies the waiting thread,
Atomics.store(sharedArray, 0, 100); Atomics.notify(sharedArray, 0, 1);
Trailing commas are allowed in parameter definitions and function calls
function func(a,b,) { // declaration console.log(a, b); } func(1,2,); // invocationBut if the function parameter definition or function call only contains a comma, a syntax error will be thrown
function func(,) { // SyntaxError: missing formal parameter console.log('no args'); }; func(,); // SyntaxError: expected expression, got ','Note: Trailing commas are not allowed in Rest Parameters and JSON.
ECMAScript 6 provides built-in support for synchronously iterating over data using iterators. Both strings and collections objects such as Set, Map, and Array come with a Symbol.iterator property which makes them iterable.
const arr = ['a', 'b', 'c', 'd']; const syncIterator = arr[Symbol.iterator](); console.log(syncIterator.next()); //{value: a, done: false} console.log(syncIterator.next()); //{value: b, done: false} console.log(syncIterator.next()); //{value: c, done: false} console.log(syncIterator.next()); //{value: d, done: false} console.log(syncIterator.next()); //{value: undefined, done: true}But these iterators are only suitable for representing synchronous data sources.
In order to access asynchronous data sources, ES2018 introduced the AsyncIterator interface, an asynchronous iteration statement (for-await-of), and async generator functions.
ES2015 or ES6 introduced both rest parameters and spread operators to convert arguments to array and vice versa using three-dot(...) notation.
-
Rest parameters can be used to convert function arguments to an array
function myfunc(p1, p2, ...p3) { console.log(p1, p2, p3); // 1, 2, [3, 4, 5, 6] } myfunc(1, 2, 3, 4, 5, 6);
-
The spread operator works in the opposite way by converting an array into separate arguments in order to pass to a function
const myArray = [10, 5, 25, -100, 200, -200]; console.log( Math.max(...myArray) ); // 200
ES2018 enables this rest/spread behavior for objects as well.
-
You can pass object to a function
function myfunc({ a, ...x }) { console.log(a, x); // 1, { b: 2, c: 3, d:4 } } myfunc({ a: 1, b: 2, c: 3, d: 4 });
-
The spread operator can be used within other objects
const myObject = { a: 1, b: 2, c: 3, d:4 }; const myNewObject = { ...myObject, e: 5 }; // { a: 1, b: 2, c: 3, d: 4, e: 5 }
Sometimes you may need to avoid duplicate code in the then() and catch() methods.
myPromise .then(result => { // process the result and then clean up the resources }) .catch(error => { // handle the error and then clean up the resources });The finally() method is useful if you want to do some processing or resource cleanup once the promise is settled(i.e either fulfilled or rejected).
Let's take a below example to hide the loading spinner after the data is fetched and processed.
let isLoading = true; fetch('http://somesite.com/users') .then(data => data.json()) .catch(err => console.error(err)) .finally(() => { isLoading = false; console.log('Finished loading!!'); })Prior to ES2019, you need to use reduce() or concat() methods to get a flat array.
function flatten(arr) { const flat = [].concat(...arr); return flat.some(Array.isArray) ? flatten(flat) : flat; } flatten([ [1, 2, 3], ['one', 'two', 'three', [22, 33] ], ['a', 'b', 'c'] ]);In ES2019, the flat() method is introduced to 'flattens' the nested arrays into the top-level array. The functionality of this method is similar to Lodash's _.flattenDepth() function. This method accepts an optional argument that specifies the number of levels a nested array should be flattened and the default nested level is 1. Note: If there are any empty slots in the array, they will be discarded.
const numberArray = [[1, 2], [[3], 4], [5, 6]]; const charArray = ['a', , 'b', , , ['c', 'd'], 'e']; const flattenedArrOneLevel = numberArray.flat(1); const flattenedArrTwoLevel = numberArray.flat(2); const flattenedCharArrOneLevel = charArray.flat(1); console.log(flattenedArrOneLevel); // [1, 2, [3], 4, 5, 6] console.log(flattenedArrTwoLevel); // [1, 2, 3, 4, 5, 6] console.log(flattenedCharArrOneLevel); // ['a', 'b', 'c', 'd', 'e']Whereas, flatMap() method combines map() and flat() into one method. It first creates a new array with the return value of a given function and then concatenates all sub-array elements of the array.
const numberArray = [[1], [2], [3], [4], [5]]; console.log(numberArray.flatMap(value => [value * 10])); // [10, 20, 30, 40, 50]In JavaScript, it is very commonn to transforming data from one format. ES2017 introduced Object.entries() method to objects into arrays.
Object to Array:
const obj = {'a': '1', 'b': '2', 'c': '3' }; const arr = Object.entries(obj); console.log(obj); // [ ['a', '1'], ['b', '2'], ['c', '3'] ]But if you want to get the object back from an array then you need iterate and convert it as below,
```js const arr = [ ['a', '1'], ['b', '2'], ['c', '3'] ]; let obj = {} for (let [key, val] of arr) { obj[key] = val; } console.log(obj); ``` We need a straightforward way to avoid this iteration. In ES2019, Object.fromEntries() method is introduced which performs the reverse of Object.entries() behavior. The above loop can be avoided easily as below,
Iterable( e.g Array or Map) to Object
const arr = [ ['a', '1'], ['b', '2'], ['c', '3'] ]; const obj = Object.fromEntries(arr); console.log(obj); // { a: "1", b: "2", c: "3" }One of the common case of this method usage is working with query params of an URL,
const paramsString = 'param1=foo¶m2=baz'; const searchParams = new URLSearchParams(paramsString); Object.fromEntries(searchParams); // => {param1: "foo", param2: "baz"}In order to make consistency with padStart/padEnd, ES2019 provided the standard functions named as trimStart and trimEnd to trim white spaces on the beginning and ending of a string. However for web compatilibity(avoid any breakage) trimLeft and trimRight will be an alias for trimStart and trimEnd respectively.
Let's see the usage with an example,
//Prior ES2019 let messageOne = " Hello World!! "; console.log(messageOne.trimLeft()); //Hello World!! console.log(messageOne.trimRight()); // Hello World!! //With ES2019 let messageTwo = " Hello World!! "; console.log(messageTwo.trimStart()); //Hello World!! console.log(messageTwo.trimEnd()); // Hello World!!While creating symbols, you also can add a description to it for debugging purposes. But there was no method to access the description directly before ES2019. Considering this, ES2019 introduced a read-only description property to retrieve a string containing the description of the Symbol.
This gives the possibility to access symbol description for different variations of Symbol objects
console.log(Symbol('one').description); // one console.log(Symbol.for('one').description); // "one" console.log(Symbol('').description); // '' console.log(Symbol().description); // unefined console.log(Symbol.iterator.description); // "Symbol.iterator"Prior to ES9, if you don't need error variable and omit the same variable then catch() clause won't be invoked. Also, the linters complain about unused variables. Inorder to avoid this problem, the optional catch binding feature is introduced to make the binding parameter optional in the catch clause. If you want to completely ignore the error or you already know the error but you just want to react to that the this feature is going to be useful.
Let's see the below syntax difference between the versions,
// With binding parameter(<ES9) try { ··· } catch (error) { ··· } // Without binding parameter(ES9) try { ··· } catch { ··· }For example, the feature detection on a browser is one of the most common case
let isTheFeatureImplemented = false; try { if(isFeatureSupported()) { isTheFeatureImplemented = true; } } catch (unused) {}JSON is used as a lightweight format for data interchange(to read and parse). The usage of JSON has been improved as part of ECMAScript specification. Basically there are 2 important changes related to JSON.
- JSON Superset
Prior to ES2019, ECMAScript claims JSON as a subset in JSON.parse but that is not true. Because ECMAScript string literals couldn’t contain the characters U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR) unlike JSON Strings. If you still use those characters then there will be a syntax error. As a workaround, you had to use an escape sequence to put them into a string.
eval('"\u2028"'); // SyntaxErrorWhereas JSON strings can contain both U+2028 and U+2029 without producing errors.
console.log(JSON.parse('"\u2028"')); // ''This restriction has been removed in ES2019. This simplifies the specification without the need of separate rules for ECMAScript string literals and JSON string literals.
- Well Formed JSON.Stringify(): Prior to ES2019, JSON.stringify method is used to return unformed Unicode strings(ill-formed Unicode strings) if there are any lone surrogates in the input.
console.log(JSON.stringify("\uD800")); // '"�"'Whereas in ES2019, JSON.stringify outputs escape sequences for lone surrogates, making its output valid Unicode and representable in UTF-8.
console.log(JSON.stringify("\uD800")); // '"\ud800"'The sort method for arrays is stable in ES2020. i.e, If you have an array of objects and sort them on a given key, the elements in the list will retain their position relative to the other objects with the same key. Now the array is using the stable TimSort algorithm for arrays over 10 elements instead of the unstable QuickSort.
Let's see an example of users retain their original position with same age group.
const users = [ { name: "Albert", age: 30 }, { name: "Bravo", age: 30 }, { name: "Colin", age: 30 }, { name: "Rock", age: 50 }, { name: "Sunny", age: 50 }, { name: "Talor", age: 50 }, { name: "John", age: 25 }, { name: "Kindo", age: 25 }, { name: "Lary", age: 25 }, { name: "Minjie", age: 25 }, { name: "Nova", age: 25 } ] users.sort((a, b) => a.age - b.age);Functions have an instance method called toString() which return a string to represent the function code. Previous versions of ECMAScript removes white spaces,new lines and comments from the function code but it has been retained with original source code in ES2020.
function sayHello(message) { let msg = message; //Print message console.log(`Hello, ${msg}`); } console.log(sayHello.toString()); // function sayHello(message) { // let msg = message; // //Print message // console.log(`Hello, ${msg}`); // }In ES6, the classes are introduced to create reusable modules and variables are declared in clousure to make them private. Where as in ES2020, private class variables are introduced to allow the variables used in the class only. By just adding a simple hash symbol in front of our variable or function, you can reserve them entirely for internal to the class.
class User { #message = "Welcome to ES2020" login() { console.log(this.#message) } } const user = new User() user.login() // Welcome to ES2020 console.log(user.#message) // Uncaught SyntaxError: Private field '#Note: As shown in the above code, If you still try to access the variable directly from the object then you will receive syntax error.
ES2020 is the current newer version of ECMAScript corresponding to the year 2020. This is the eleventh edition of the ECMAScript Language Specification. Even though this release doesn't bring as many features as ES6, it included some really useful features.
Most of these features already supported by some browsers and try out with babel parser support for unsupported features. This edition is set for final approval by the ECMA general assembly in June, 2020. The ECMAScript 2020 (ES2020) language specification is ready now.
In earlier JavaScript version, there is a limitation of using the Number type. i.e, You cannot safely represent integer values(Number primitive) larger than pow(2, 53). In ES2020,
BigInt is introduced as the 7th primitive type to represent whole numbers(integers with arbitrary precision) larger than pow(2, 53) - 1(or 9007199254740991 or Number.MAX_SAFE_INTEGER). This is been created by appending n to the end of an integer literal or by calling the function BigInt().
// 1. Current number system const max = Number.MAX_SAFE_INTEGER; console.log(max + 1) // 9007199254740992 console.log(max + 2) // 9007199254740992 // 2. BigInt representation const bigInt = 9007199254740991n; const bigIntConstructorRep = BigInt(9007199254740991); // 9007199254740991n const bigIntStringRep = BigInt("9007199254740991"); // 9007199254740991n // 3. Typeof usage console.log(typeof 1)// number console.log(typeof 1n)// bigint console.log(typeof BigInt('1'))// bigint // 4. Operators const previousMaxNum = BigInt(Number.MAX_SAFE_INTEGER); console.log(previousMaxNum + 2n); //9007199254740993n (this was not possible before) console.log(previousMaxNum -2n); //9007199254740990n console.log(previousMaxNum * 2n); //18014398509481982n console.log(previousMaxNum % 2n); //1n console.log(previousMaxNum / 2n); // 4503599627370495n // 5. comparison console.log(1n === 1); // false console.log(1n === BigInt(1)); // true console.log(1n == 1); // trueStatic imports supports some of the important use cases such as static analysis, bundling tools, and tree shaking, it is also it's desirable to be able to dynamically load parts of a JavaScript application at runtime.
The new feature dynamic import is introduced to load a module conditionally or on demand. Since it returns a promise for the module namespace object of the requested module, the module can be resolved or import can now be assigned to a variable using async/await as below
<script> const moduleSpecifier = './message.js'; import(moduleSpecifier) .then((module) => { module.default(); // Hello, default export module.sayGoodBye(); //Bye, named export }) .catch( err => console.log('loading error')); </script><script> (async function() { const moduleSpecifier = './message.js'; const messageModule = await import(moduleSpecifier); messageModule.default(); // Hello, default export messageModule.sayGoodBye(); //Bye, named export })(); </script>and the imported module appears with both default and named exports
export default () => { return "Hello, default export"; } export const sayGoodBye = () => { return "Bye, named export" }Note: Dynamic import does not require scripts of type="module"
The nullish coalescing operator (??) is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand. This operator replaces || operator to provide default values if you treat empty value or '', 0 and NaN as valid values. This is because the logical OR(||) operator treats(empty value or '', 0 and NaN) as falsy values and returns the right operand value which is wrong in this case. Hence, this operator truely checks for nullish values instead falsy values.
let vehicle = { car: { name: "", speed: 0 } }; console.log(vehicle.car.name || "Unknown"); // Unknown console.log(vehicle.car.speed || 90); // 90 console.log(vehicle.car.name ?? "Unknown"); // ""(empty is valid case for name) console.log(vehicle.car.speed ?? 90); // 0(zero is valid case for speed)In a short note, nullish operator returns a non-nullish value and || operator returns truthy values.
There is String#match method to get all the matches of a string against a regular expression by iterating for each match. However this method gives you the substrings that match.
The String#matchAll() is a new method added to String prototype, which returns an iterator of all results matching a string against a regular expression.
const regex = /t(e)(st(\d?))/g; const string = 'test1test2'; const matchesIterator = string.matchAll(regex); Array.from(matchesIterator, result => console.log(result));When you this code in browser console, the matches iterator produces an array for each match including the capturing groups with a few extras.
["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined] ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]In JavaScript, Long chains of property accesses is quite error-prone if any of them evaluates to null or undefined value. Also, it is not a good idea to check property existence on each item which in turn leads to a deeply-nested structured if statements.
Optional chaining is a new feature that can make your JavaScript code look cleaner and robust by appending(?.) operator to stop the evaluation and return undefined if the item is undefined or null. By the way, this operator can be used together with nullish coalescing operator to provide default values
let vehicle = { }; let vehicle1 = { car: { name: 'ABC', speed: 90 } }; console.log(vehicle.car?.name); // TypeError: Cannot read property 'name' of undefined console.log(vehicle.car?.name); // Undefined console.log(vehicle.car?.speed); // Undefined console.log(vehicle1.car?.name); // ABC console.log(vehicle1.car?.speed); // 90 console.log(vehicle.car?.name ?? "Unknown"); // Unknown console.log(vehicle.car?.speed ?? 90); // 90It is really helpful to log(especially to debug errors) about each promise when you are handling multiple promises. The Promise.allSettled() method returns a new promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects describing the outcome of each promise.
const promise1 = new Promise((resolve, reject) => setTimeout(() => resolve(100), 1000)); const promise2 = new Promise((resolve, reject) => setTimeout(reject, 1000)); Promise.allSettled([promise1, promise2]).then(data => console.log(data)); // [ Object { status: "fulfilled", value: 100}, Object { status: "rejected", reason: undefined} ]As per the output, each outcome object returns status field which denotes either "fulfilled"(value present) or "rejected"(reason present)
Prior to ES2020, you need to write different syntax in different JavaScript environments(cross-platforms) just to access the global object. It is really a hard time for developers because you need to use window, self, or frames on the browser side, global on the nodejs, self on the web workers side.
On the other hand, this keyword can be used inside functions for non-strict mode but it gives undefined in strict mode. If you think about Function('return this')() as a solution for above environments, it will fail for CSP enabled environments(where eval() is disabled).
In the older versions, you can use es6-shim as below,
var getGlobal = function () { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } throw new Error('unable to locate global object'); }; var globals = getGlobal(); if (typeof globals.setTimeout !== 'function') { console.log('no setTimeout in this environment or runtime'); }In ES2020, globalThis property is introduced to provide a standard way of accessing the global this value across environments.
if (typeof globalThis.setTimeout !== 'function') { console.log('no setTimeout in this environment or runtime'); }The import.meta object was created by the ECMAScript implementation with a null prototype to get context-specific metadata about a JavaScript module. Let's say you are trying to load my-module from a script,
<script type="module" src="my-module.js"></script>Now you can access meta information(base URL of the module) about the module using the import.meta object
console.log(import.meta); // { url: "file:///home/user/my-module.js" }The above URL can be either URL from which the script was obtained (for external scripts), or the document base URL of the containing document (for inline scripts).
Note: Remember import is not really an object but import.meta is provided as an object which is extensible, and its properties are writable, configurable, and enumerable.
Prior to ES2020, the specifications did not specify in which order for (a in b) should run. Even though most of the javascript engines/browsers loop over the properties of an object in the order in which they were defined, it is not the case with all scenarios. This has been officially standardized in ES2020.
var object = { 'a': 2, 'b': 3, 'c': 4 } for(let key in object) { console.log(key); // a b c }