- Notifications
You must be signed in to change notification settings - Fork2
coderaishya/ECMAScript-features
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
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 theactions tab.
npm installnpx babel-node es2020/bigint // Try other examples too
ECMAScript 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.
Edition | Date |
---|---|
ES2015 Or ES6 | June 2015 |
ES2016 Or ES7 | June 2016 |
ES2017 Or ES8 | June 2017 |
ES2018 Or ES9 | June 2018 |
ES2019 Or ES10 | June 2019 |
ES2020 Or ES11 | June 2020 |
ES2021 Or ES12 | June 2021 |
The variable scoping determines the visibility or accessibility of a variable within the certain part of the program or region.
In ES6, both
const
andlet
keywords allow developers to declare variables in the block scope.The
let
statement declares a block-scoped local variable which can be reassigned. i.e,let
declaration creates a mutable variable.leta=1;if(a===1){leta=2;console.log(a);//2}console.log(a);//1
const variables are similar tolet variables but they can't be changed through reassignment. i.e, The const declaration creates a read-only reference to a value.
constx=1;if(x===1){consty=2;// You cannot re-assign the value similar to let variableconsole.log(y);//2}console.log(x);//1
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 ExpressionvarmultiplyFunc=function(a,b){returna*b;}console.log(multiplyFunc(2,5));// 10// Arrow functionvarmultiplyArrowFunc=(a,b)=>a*b;console.log(multiplyArrowFunc(2,5));// 10
You 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 statementconstmessage=name=>console.log("Hello, "+name+"!");message("Sudheer");// Hello, Sudheer!//2. Multiple parameters and single statementconstmultiply=(x,y)=>x*y;console.log(multiply(2,5));// 10//3. Single parameter and multiple statementsconsteven=number=>{if(number%2){console.log("Even");}else{console.log("Odd");}}even(5);// odd//4. Multiple parameters and multiple statementsconstdivide=(x,y)=>{if(y!=0){returnx/y;}}console.log(divide(100,5));// 20//5. No parameter and single statementconstgreet=()=>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.
There are two ways to define classes,
- Class declarations:
classSquare{constructor(length){this.length=length;}getarea(){returnthis.length*this.length;}setarea(value){this.area=value;}}
- Class expressions:
constsquare=classSquare{constructor(length){this.length=length;}getarea(){returnthis.length*this.length;}setarea(value){this.area=value;}}
You can useextend keyword to use inheritance. This enables the subclass to get all features of a parent class.
classVehicle{constructor(name){this.name=name;}start(){console.log(`${this.name} vehicle started`);}}classCarextendsVehicle{start(){console.log(`${this.name} car started`);}}constcar=newCar('BMW');console.log(car.start());// BMW car started
Note: Even though ES6 classes looks similar to classes in other object oriented languages, such as Java, PHP, etc but they do not work exactly the same way.
Object literals are extended to support setting the prototype at construction, shorthand for foo: foo assignments, defining methods, making super calls, and computing property names with expressions.
The important enhancements of object literals are,
- Property Shorthand:
Object's properties are often created from variables with the same name.
Let's see the ES5 representation
vara=1,b=2,c=3,obj={a:a,b:b,c:c};console.log(obj);
and it can be represented in a shorter syntax as below,
consta=1,b=2,c=3;constobj={ a, b, c};console.log(obj);
- Method Shorthand:In ES5, Object methods require the function statement as below,
varcalculation={sum:function(a,b){returna+b;},multiply:function(a,b){returna*b;}};console.log(calculation.add(5,3));// 8console.log(calculation.multiply(5,3));// 15
This can be avoided in ES6,
constcalculation={sum(a,b){returna+b;},multiply(a,b){returna*b;}};console.log(calculation.add(5,3));// 8console.log(calculation.multiply(5,3));// 15
- Computed Property Names:In ES5, it wasn’t possible to use a variable for a key name during object creation stage.
varkey='three',obj={one:1,two:2};obj[key]=3;
Object keys can be dynamically assigned in ES6 by placing an expression in square brackets([])
constkey='three',computedObj={one:1,two:2,[key]:3};
Prior to ES6, JavaScript developers would need to do ugly string concatenation to creat dynamic strings.
Template literals allows you to work with strings in a new way compared to ES5. These are just string literals allowing embedded expressions denoted by the dollar sign and curly braces (${expression}). Also, these literals are enclosed by the backtick (
) character instead of double or single quotes.
ES6 has two new kinds of literals:
- Template literals: string literals which exists across multiple lines and include interpolated expressions(i.e, ${expression})
constfirstName='John';console.log(`Hello${firstName}!Good morning!`);
- Tagged template literals: Function calls which are created by mentioning a function before a template literal.
The real world use case is creating components in CSS-In-JS styled components to use across the application
constButton=styled.a` display: inline-block; border-radius: 3px;`
Destructuring is a javascript expression for extracting multiple values from data stored in objects(properties of an object) and Arrays.
Object destructuring:
This feature is used to extract values from an object.
constuser={firstName:'John',lastName:'Kary'};const{firstName, lastName}=user;console.log(firstName,lastName);// John, Kary
Array destructuring:
This feature is used to extract values from an array.
const[one,two,three]=['one','two','three'];console.log(one,two,three);// one, two, three
You can use destructing in below places,
- Variable declarations
- Assignments
- Parameter definitions
- for-of loop
Default parameters allow named parameters of a function to be initialized with default values if no value or undefined is passed.
Prior to ES6, you need check for undefined values and provide the default value for undefined values using if/else or ternary operator
functionadd(a,b){a=(typeofa!=='undefined') ?a :10;b=(typeofb!=='undefined') ?b :20;returna+b;}add(20);// 40add();// 30
In ES6, these checks can be avoided using default parameters
functionadd(a=10,b=20){returna+b;}add(20);// 40add();// 30
The rest parameter is used to represent an indefinite number of arguments as an array. The important point here is only the function's last parameter can be a "rest parameter". This feature has been introduced to reduce the boilerplate code that was induced by the arguments.
functionsum(...args){returnargs.reduce((previous,current)=>{returnprevious+current;});}console.log(sum(1,2,3));// 6console.log(sum(1,2,3,4));// 10console.log(sum(1,2,3,4,5));// 15
Spread Operator allows iterables( arrays / objects / strings ) to be expanded into single arguments/elements.
- In function and constructor calls, the spread operator turns iterable values into arguments
console.log(Math.max(...[-10,30,10,20]));//30console.log(Math.max(-10, ...[-50,10],30));//30
- In Array literals and strings, the spread operator turns iterable values into Array elements
console.log([1, ...[2,3],4, ...[5,6,7]]);// 1, 2, 3, 4, 5, 6, 7
Note: The spread syntax is opposite of rest parameter.
String, Array, TypedArray, Map, and Set are all built-in iterables but objects are not iterables by default.Iterators are a new way to loop over any collection in JavaScript. These are objects which defines a sequence and potentially a return value upon its termination.An iterator implements the Iterator protocol by having a next() method that returns an object with two properties:
- value: The next value in the iteration sequence.
- done: returns rue if the last value in the sequence has already been consumed.
You can make the object iterable by defining a
Symbol.iterator
property on it.constcollection={one:1,two:2,three:3,[Symbol.iterator](){constvalues=Object.keys(this);leti=0;return{next:()=>{return{value:this[values[i++]],done:i>values.length}}};}};constiterator=collection[Symbol.iterator]();console.log(iterator.next());// → {value: 1, done: false}console.log(iterator.next());// → {value: 2, done: false}console.log(iterator.next());// → {value: 3, done: false}console.log(iterator.next());// → {value: undefined, done: true}for(constvalueofcollection){console.log(value);}
The for...of statement creates a loop iterating over user defined collection object. But this loop can be used for built-in objects too.
Note: The abrupt iteration termination can be caused by break, throw or return.
A generator is a function that can stop or suspend midway and then continue from where it stopped while maintaining the context(saved across re-entrances). It can be defined using a function keyword followed by an asterisk(i.e, function* ()).
This function returns an iterator object and this iterator'snext() method returns an object with a value property containing the yielded value and a done property which indicates whether the generator has yielded its last value.
function*myGenerator(i){yieldi+10;yieldi+20;returni+30;}constmyGenObj=myGenerator(10);console.log(myGenObj.next().value);// 20console.log(myGenObj.next().value);// 30console.log(myGenObj.next().value);// 40
Note: We can use
yield*
to delegate to another generator functionModules are small units of independent, reusable code to be used as the building blocks in a Javascript application.
Prior to ES6, there was no native modules support in JavaScript. There were 3 major module standards used,
- Asynchronous Module Definition (AMD)
- RequireJS Modules
- CommonJS Modules (module.exports and require syntax used in Node.js)
ES6 has provided the built-in support for modules. Everything inside a module is private by default, and runs in strict mode. Public variables, functions and classes are exposed using
export
statement and import the same usingimport
statement.Export Statement:
There are two types of exports:
- Named Exports (Zero or more exports per module)
You can export each element or a single export statement to export all the elements at once
// module "my-module.js"constPI=Math.PI;functionadd(...args){returnargs.reduce((num,tot)=>tot+num);}functionmultiply(...args){returnargs.reduce((num,tot)=>tot*num);}// private functionfunctionprint(msg){console.log(msg);}export{PI,add,multiply};
- Default Exports (One per module)
If we want to export a single value, you could use a default export
// module "my-module.js"exportdefaultfunctionadd(...args){returnargs.reduce((num,tot)=>tot+num);}
Import Statement:
The static import statement is used to import read only live bindings which are exported by another module.
There are many variations of import scenarios as below,
// 1. Import an entire module's contentsimport*asnamefrom"my-module";//2.Import a single export from a moduleimport{export1}from"my-module";//3.Import multiple exports from a moduleimport{export1,export2}from"my-module";//4.Import default export from a moduleimportdefaultExportfrom"my-module";//5.Import an export with an aliasimport{export1asalias1}from"my-module";
Set is a built-in object to store collections of unique values of any type.
letmySet=newSet()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)// 4console.log(mySet.has(2));// true
The 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.
letmyUserSet=newWeakSet();letjohn={name:"John"};letrocky={name:"Rocky"};letalex={name:"Alex"};letnick={name:"Nick"};myUserSet.add(john);myUserSet.add(rocky);myUserSet.add(john);myUserSet.add(nick);console.log(myUserSet.has(john));// trueconsole.log(myUserSet.has(alex));// falseconsole.log(myUserSet.delete(nick));console.log(myUserSet.has(nick));// falsejohn=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,
lettypeMap=newMap();varkeyObj={'one':1}typeMap.set('10','string');// a string keytypeMap.set(10,'number');// a numeric keytypeMap.set(true,'boolean');// a boolean keytypeMap.set(keyObj,'object');// an object keyconsole.log(typeMap.get(10));// numberconsole.log(typeMap.get('10'));// stringconsole.log(typeMap.get(keyObj))// objectconsole.log(typeMap.get({'one':1}))// undefinedconsole.log(typeMap.size);// 3for(letitemoftypeMap){console.log(item);}for(letitemintypeMap){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,
varweakMap=newWeakMap();varobj1={}varobj2={}weakMap.set(obj1,1);weakMap.set(obj2,2);weakMap.set({},{"four":4});console.log(weakMap.get(obj2));// 2console.log(weakMap.has({}));// return false even though empty object exists as key. Because the keys have different referencesdeleteobj2;console.log(weakMap.get(obj2));// 2weakMap.delete(obj1)console.log(weakMap.get(obj1));//undefined
Prior 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).
letstr='𠮷';console.log(str.length);// 2console.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 2console.log('\u20BB7); // 7!(wrong value)console.log(str==='\uD842\uDFB7');// true
ECMAScript 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.
letstr='𠮷';// new string formconsole.log('\u{20BB7}');// "𠮷"// new RegExp u modeconsole.log(newRegExp('\u{20BB7}','u'));console.log(/^.$/u.test(str));// true//API methodsconsole.log(str.codePointAt(0));// 134071console.log(str.codePointAt(1));// 57271console.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 propertiesletid=Symbol("id");letuser={name:"John",age:40,[id]:111};for(letkeyinuser){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 constantsconstlogLevels={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 Checksconsole.log(Symbol('foo')===Symbol('foo'));// falseconsole.log(Symbol.for('foo')===Symbol.for('foo'));// true
The Proxy object is used to create a proxy for another object, which can intercept and redefine fundamental operations for that object such as property lookup, assignment, enumeration, function invocation etc. These are used in many libraries and some browser frameworks.
The proxy object is created with two parameters with below syntax,
letproxy=newProxy(target,handler)
- target: Object on which you want to proxy
- handler: An object that defines which operations will be intercepted and how to redefine them.
The property Lookup Behavior of a user proxied object will be as below,
consttarget={name:"John",age:3};consthandler={get:function(target,prop){returnpropintarget ?target[prop] :`${prop} does not exist`;}};constuser=newProxy(target,handler);console.log(user.name);// Johnconsole.log(user.age);// Johnconsole.log(user.gender);// gender does not exist
These proxies also enforce value validations. Let's take an example with set handler,
letageValidator={set:function(obj,prop,value){if(prop==='age'){if(!Number.isInteger(value)){thrownewTypeError('The age is not an integer');}if(value>200){thrownewRangeError('Invalid age');}}obj[prop]=value;// The default behavior to store the valuereturntrue;// Indicate success}};constperson=newProxy({},validator);person.age=30;console.log(person.age);// 30person.age='old';// Throws an exceptionperson.age=200;// Throws an exception
A promise is an object which represent the eventual completion or failure of an asynchronous operation.
It is in one of these states:
pending: Represents initial state, neither fulfilled nor rejected.fulfilled: Indicates that the operation is completed successfully.rejected: Indicates that the operation is failed.
A promise is said to be settled if it is either fulfilled or rejected, but not pending. The instance methods
promise.then()
,promise.catch()
, andpromise.finally()
are used to associate further action with a promise that becomes settled. And these methods also return a newly generated promise object, which can optionally be used for chaining.The promise chaining structure would be as below,
constpromise=newPromise(function(resolve,reject){setTimeout(()=>resolve(1),1000);});promise.then(function(result){console.log(result);// 1returnresult*2;}).then(function(result){console.log(result);// 2returnresult*3;}).then(function(result){console.log(result);// 6returnresult*4;}).catch(function(error){console.log(error);});
Reflection 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.Unlike most global objects, Reflect is not a constructor. i.e, You cannot use Reflect with the new operator or invoke the Reflect as a function. It is similar to Math and JSON objects in which all the methods of this object are static.
Let's see the usage of Reflect API with below examples,
- Creating objects using Reflect.construct();
The
construct()
method behaves like the regular new operator, but as a function. It is equivalent to calling new target(...args) with an option to specify a different prototype. The syntax looks like as below,Reflect.construct(target,args[,newTarget]);
The method has below parameters,
- target: The target function to call.
- argumentsList: An array-like object specifying the arguments with which target should be called.
- newTarget: The constructor whose prototype should be used. This is an optional parameter. i.e, If newTarget is not present, its value defaults to target.
Example:
classUser{constructor(firstName,lastName){this.firstName=firstName;this.lastName=lastName;}getfullName(){return`${this.firstName}${this.lastName}`;}};letargs=['John','Emma'];letjohn=Reflect.construct(User,args);console.log(johninstanceofUser);console.log(john.fullName);// John Doe
- Calling a function using Reflect.apply():Prior to ES6, you can invoke a function with a specified
this
value and arguments by using theFunction.prototype.apply()
method.
For example, you can call
max()
static method of Math object,constmax=Function.prototype.apply.call(Math.max,Math,[100,200,300]);console.log(max);
In ES6, Reflect.apply() provides the same features as Function.prototype.apply() but in a less verbose syntax.
constmax=Reflect.apply(Math.max,Math,[100,200,300]);console.log(max);
- Defining a property using Reflect.defineProperty():The
Reflect.defineProperty()
method is similar toObject.defineProperty()
but it returns a Boolean value indicating whether or not the property was defined successfully instead of throwing an exception.
The syntax of this method looks like below,
Reflect.defineProperty(target,propertyName,propertyDescriptor)
Let's define the age property on user object,
classUser{constructor(firstName,lastName){this.firstName=firstName;this.lastName=lastName;}getfullName(){return`${this.firstName}${this.lastName}`;}};letjohn=newUser('John','Resig');if(Reflect.defineProperty(john,'age',{writable:true,configurable:true,enumerable:false,value:33,})){console.log(john.age);}else{console.log('Cannot define the age property on the user object.');}
- Delete property using Reflect.deleteProperty():
The
Reflect.deleteProperty()
method is used to delete properties like the delete operator but as a function. It returns Boolean value indicating whether or not the property was successfully deleted.constuser={name:'John',age:33};console.log(Reflect.deleteProperty(user,'age'));// trueconsole.log(user.age);// undefined
- Get property of an object using Reflect.get():The
Reflect.get
method is used to get a property on an object like the property accessor syntax but as a function.
constuser={name:'John',age:33};console.log(Reflect.get(user,'age'));// 33
- :
ES5 provided numeric literals in octal (prefix 0), decimal (no prefix), and hexadecimal ( 0x) representation. ES6 added support for binary literals and improvements on octal literals.
1. Binary literals:
Prior to ES5, JavaScript didn’t provide any literal form of binary numbers. So you need to use a binary string with the help of
parseInt()
constnum=parseInt('110',2);console.log(num);// 6
Whereas ES6 added support for binary literals using the0b prefix followed by a sequence of binary numbers (i.e, 0 and 1).
constnum=0b110;console.log(num);// 6
2. Octal literals:
In ES5, to represent an octal literal, you use the zero prefix (0) followed by a sequence of octal digits (from 0 to 7).
constnum=055;console.log(num);// 45letinvalidNum=058;console.log(invalidNum);// treated as decimal 58
Whereas ES6 represents the octal literal by using the prefix0o followed by a sequence of octal digits from 0 through 7.
constnum=055;console.log(num);// 45constinvalidNum=028;console.log(invalidNum);// treated as decimal 28
Remember If you use an invalid number in the octal literal, JavaScript will throw a SyntaxError as below,
constinvalidNum=028;console.log(invalidNum);// SyntaxError
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 upton * factorial(n - 1)
functionfactorial(n){if(n===0){return1}returnn*factorial(n-1)}console.log(factorial(5));//120
But if you use Tail recursion functions, they keep passing all the necessary data it needs down the recursion without relying on the stack.
functionfactorial(n,acc=1){if(n===0){returnacc}returnfactorial(n-1,n*acc)}console.log(factorial(5));//120
The 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));
- ES6 introduced few array methods and two of them are
Array.find()
andArray.findIndex()
.
Array.find()This method returns the value of the first element in an array that satisfies the given test. Let's take an example of array with all even elements except one element and usefind
method to find the odd element.
```jslet arr = [2, 4, 5, 6, 8, 10];function isOdd(i) { return i % 2 !== 0;}console.log(arr.find(isOdd)); // 5```
Array.findIndex()
This method returns the index of the first element in the array that satisfies the given test. Let's take an example of array with all even elements except one element and usefindIndex
method to find the index of odd element.
```jslet arr = [2, 4, 5, 6, 8, 10];function isOdd(i) { return i % 2 !== 0;}console.log(arr.findIndex(isOdd)); //2```
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.constarray=[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.constarray=[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 thanArray.prototype.indexOf()
methods. i.e, If the array contains NaN and Undefined values thenindexOf()
does not return correct index while searching for NaN and Undefined.
letnumbers=[1,2,3,4,NaN,,];console.log(numbers.indexOf(NaN));// -1console.log(numbers.indexOf(undefined));// -1
On the otherhand,includes
method is able to find these elements
letnumbers=[1,2,3,4,NaN,,];console.log(numbers.includes(NaN));// trueconsole.log(numbers.includes(undefined));// true
The 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 ES7constcube=x=>Math.pow(x,3);console.log(cube(3));// 27//Using ES7constcube1=x=>x**3;console.log(cube1(3));// 27
In 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
functiontask(){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 theasync
keyword which enable asynchronous, promise-based behavior to be written in a cleaner style by avoiding promise chains. These functions can contain zero or moreawait
expressions.
Let's take a below async function example,
asyncfunctionlogger(){letdata=awaitfetch('http://someapi.com/users');// pause until fetch returnsconsole.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.constcountries={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 asfor...in
loop.constcountries={IN:'India',SG:'Singapore',}Object.entries(countries)// [["IN", "India"], ["SG", "Singapore"]]
By the way, non-object argument will be coerced to an object
constcountriesArr=['India','Singapore'];console.log(Object.entries(countriesArr));// [ ['0', 'India'], ['1', 'Singapore']]constcountry='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 properties
Property 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,
constprofile={age:42};constdescriptors=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,
constcardNumber='01234567891234';constlastFourDigits=cardNumber.slice(-4);constmaskedCardNumber=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
constlabel1="Name";constlabel2="Phone Number";constvalue1="John"constvalue2="(222)-333-3456";console.log((label1+': ').padEnd(20,' ')+value1);// Name: Johnconsole.log(label2+": "+value2);// Phone Number: (222)-333-3456
The 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.
constsharedMemory=newSharedArrayBuffer(1024);constsharedArray=newUint8Array(sharedMemory);sharedArray[0]=10;Atomics.add(sharedArray,0,20);console.log(Atomics.load(sharedArray,0));// 30Atomics.sub(sharedArray,0,10);console.log(Atomics.load(sharedArray,0));// 20Atomics.and(sharedArray,0,5);console.log(Atomics.load(sharedArray,0));// 4Atomics.or(sharedArray,0,1);console.log(Atomics.load(sharedArray,0));// 5Atomics.xor(sharedArray,0,1);console.log(Atomics.load(sharedArray,0));// 4Atomics.store(sharedArray,0,10);// 10Atomics.compareExchange(sharedArray,0,5,10);console.log(Atomics.load(sharedArray,0));// 10Atomics.exchange(sharedArray,0,10);console.log(Atomics.load(sharedArray,0));//10Atomics.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
constsharedMemory=newSharedArrayBuffer(1024);constsharedArray=newInt32Array(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
functionfunc(a,b,){// declarationconsole.log(a,b);}func(1,2,);// invocation
But if the function parameter definition or function call only contains a comma, a syntax error will be thrown
functionfunc1(,){// SyntaxError: missing formal parameterconsole.log('no args');};func1(,);// 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.
constarr=['a','b','c','d'];constsyncIterator=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
functionmyfunc(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
constmyArray=[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
functionmyfunc1({ a, ...x}){console.log(a,x);// 1, { b: 2, c: 3, d:4 }}myfunc1({a:1,b:2,c:3,d:4});
The spread operator can be used within other objects
constmyObject={a:1,b:2,c:3,d:4};constmyNewObject={ ...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.
letisLoading=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.functionflatten(arr){constflat=[].concat(...arr);returnflat.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.constnumberArray=[[1,2],[[3],4],[5,6]];constcharArray=['a',,'b',,,['c','d'],'e'];constflattenedArrOneLevel=numberArray.flat(1);constflattenedArrTwoLevel=numberArray.flat(2);constflattenedCharArrOneLevel=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()
andflat()
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.constnumberArray1=[[1],[2],[3],[4],[5]];console.log(numberArray1.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:
constobj={'a':'1','b':'2','c':'3'};constarr=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,
constarr=[['a','1'],['b','2'],['c','3']];letobj={}for(let[key,val]ofarr){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 ofObject.entries()
behavior. The above loop can be avoided easily as below,
Iterable( e.g Array or Map) to Object
constarr=[['a','1'],['b','2'],['c','3']];constobj=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,
constparamsString='param1=foo¶m2=baz';constsearchParams=newURLSearchParams(paramsString);Object.fromEntries(searchParams);// => {param1: "foo", param2: "baz"}
In order to make consistency with padStart/padEnd, ES2019 provided the standard functions named as
trimStart
andtrimEnd
to trim white spaces on the beginning and ending of a string. However for web compatilibity(avoid any breakage)trimLeft
andtrimRight
will be an alias fortrimStart
andtrimEnd
respectively.Let's see the usage with an example,
//Prior ES2019letmessageOne=" Hello World!! ";console.log(messageOne.trimLeft());//Hello World!!console.log(messageOne.trimRight());// Hello World!!//With ES2019letmessageTwo=" 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);// oneconsole.log(Symbol.for('one').description);// "one"console.log(Symbol('').description);// ''console.log(Symbol().description);// unefinedconsole.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
letisTheFeatureImplemented=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) andU+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"');// SyntaxError
Whereas 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 unstableQuickSort
.Let's see an example of users retain their original position with same age group.
constusers=[{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.functionsayHello(message){letmsg=message;//Print messageconsole.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.
classUser{ #message="Welcome to ES2020"login(){console.log(this.#message)}}constuser=newUser()user.login()// Welcome to ES2020console.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. TheECMAScript 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 appendingn
to the end of an integer literal or by calling the function BigInt().// 1. Current number systemconstmax=Number.MAX_SAFE_INTEGER;console.log(max+1)// 9007199254740992console.log(max+2)// 9007199254740992// 2. BigInt representationconstbigInt=9007199254740991n;constbigIntConstructorRep=BigInt(9007199254740991);// 9007199254740991nconstbigIntStringRep=BigInt("9007199254740991");// 9007199254740991n// 3. Typeof usageconsole.log(typeof1)// numberconsole.log(typeof1n)// bigintconsole.log(typeofBigInt('1'))// bigint// 4. OperatorsconstpreviousMaxNum=BigInt(Number.MAX_SAFE_INTEGER);console.log(previousMaxNum+2n);//9007199254740993n (this was not possible before)console.log(previousMaxNum-2n);//9007199254740990nconsole.log(previousMaxNum*2n);//18014398509481982nconsole.log(previousMaxNum%2n);//1nconsole.log(previousMaxNum/2n);// 4503599627370495n// 5. comparisonconsole.log(1n===1);// falseconsole.log(1n===BigInt(1));// trueconsole.log(1n==1);// true
Static 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 exportmodule.sayGoodBye();//Bye, named export}) .catch(err =>console.log('loadingerror'));</script>
<script>(async function(){constmoduleSpecifier='./message.js';constmessageModule=awaitimport(moduleSpecifier);messageModule.default();// Hello, default exportmessageModule.sayGoodBye();//Bye, named export})();</script>
and the imported module appears with both default and named exports
exportdefault()=>{return"Hello, default export";}exportconstsayGoodBye=()=>{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
orundefined
, 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 fornullish
values insteadfalsy
values.letvehicle={car:{name:"",speed:0}};console.log(vehicle.car.name||"Unknown");// Unknownconsole.log(vehicle.car.speed||90);// 90console.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.constregex=/t(e)(st(\d?))/g;conststring='test1test2';constmatchesIterator=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
orundefined
value. Also, it is not a good idea to check property existence on each item which in turn leads to a deeply-nested structuredif
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
letvehicle={};letvehicle1={car:{name:'ABC',speed:90}};console.log(vehicle.car?.name);// TypeError: Cannot read property 'name' of undefinedconsole.log(vehicle.car?.name);// Undefinedconsole.log(vehicle.car?.speed);// Undefinedconsole.log(vehicle1.car?.name);// ABCconsole.log(vehicle1.car?.speed);// 90console.log(vehicle.car?.name??"Unknown");// Unknownconsole.log(vehicle.car?.speed??90);// 90
It 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.constpromise1=newPromise((resolve,reject)=>setTimeout(()=>resolve(100),1000));constpromise2=newPromise((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 aboutFunction('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,
vargetGlobal=function(){if(typeofself!=='undefined'){returnself;}if(typeofwindow!=='undefined'){returnwindow;}if(typeofglobal!=='undefined'){returnglobal;}thrownewError('unable to locate global object');};varglobals=getGlobal();if(typeofglobals.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(typeofglobalThis.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 loadmy-module
from a script,<scripttype="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 butimport.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.
varobject={'a':2,'b':3,'c':4}for(letkeyinobject){console.log(key);// a b c}
ECMAScript 2021 or ES12 has been released in mid of 2021 with few important features which can be used JavaScript.
The new
replaceAll()
method fromString
prototype is used to replace all the occurrences of a string from another string value. Earlier it was not possible to replace all the instances of a substring without the use of regex.Before ES2021
console.log('10101010'.replace(newRegExp('0','g'),'1'));// 11111111console.log('01010101'.replace(/0/g,'1'));// 11111111
After ES2021
console.log('10101010'.replaceAll('0','1'));// 11111111console.log('01010101'.replaceAll('0','1'));// 11111111
The new
promise.any
method takes multiple promises and resolves to the value of the first promise which is successfully fulfilled.letpromise1=newPromise((resolve)=>setTimeout(resolve,100,'Resolves after 100ms'));letpromise2=newPromise((resolve)=>setTimeout(resolve,200,'Resolves after 200ms'));letpromise3=newPromise((resolve,reject)=>setTimeout(reject,0));letpromises=[promise1,promise2,promise3];Promise.any(promises).then(value=>console.log(value));// Resolves after 100ms
In case none of the promises resolved then it throws
AggregateError
exception.(async()=>{try{constoutput=awaitPromise.any([Promise.reject('Error 1'),Promise.reject('Error 2'),Promise.reject('Error 3'),]);console.log(`Output:${output}`);}catch(err){console.log(`Error:${err.errors}`);}})();// Error: Error1,Error2,Error3
WeakRef provides two new pieces of functionality
- creating weak references to objects with the WeakRef class
- running user-defined finalizers after objects are garbage-collected, with the FinalizationRegistry class
WeakRef:weak reference is a reference to an object that doesn’t prevent garbage collection if it is the only reference to the object in the memory.It’s useful when we don’t want to keep the object in memory forever(e.g, WebSocket). The main use of weak references is to implement caches or mappings to large objects for which you don't need to keep it in memory for rarely used objects.
Prior to ES12, WeakMaps and WeakSets are the only way to kind-of-weakly reference an object in JavaScript. Whereas WeakRef in ES12 provides actual weak references, enabling a window into the lifetime of an object.
Let's see an example of a weak reference object using
WeakRef
constructor and read the reference usingderef()
methodconstmyObject=newWeakRef({name:‘Sudheer’,age:34});console.log(myObject.deref());//output: {name: “Sudheer”, age: 35}console.log(myObject.deref().name);//output: Sudheer
Finalizers:A
FinalizationRegistry
object lets you request a callback when an object is garbage-collected. It works as a cleanup callback.// Create new FinalizationRegistry:constreg=newFinalizationRegistry((val)=>{console.log(val);});(()=>{// Create new object:constobj={}// Register finalizer for the "obj" as first argument and value for callback function as second argument:reg.register(obj,'obj has been garbage-collected.')})();
Note: The finalization callback does not run immediately after garbage-collecting the event listener, so don't use it for important logic or metrics.
Numeric separators are helpful to read large numbers(or numeric literals) in JavaScript by providing separation between digits using underscores(_). In otherwords, numeric literals are more readable by creating a visual separation between groups of digits.
For example, one billion and one trillion becomes more readable with _ numeric separator,
constbillion=1000_000_000;console.log(billion);// 1000000000consttrillion=1000_000_000_000n;// BigInt numberconsole.log(trillion);// 1000000000000
It can be used for binary and hex literals as well.
constbinaryLiteral=0b1010_1010;console.log(binaryLiteral);consthexLiteral=0xFF_FF_FF_FF;console.log(hexLiteral);
Note: The separator can be placed anywhere within the number for readability purpose.
Logical assignment operators combines the logical operations(&&, || or ??) with assignment. They are quite useful for assigning default values to variables.
&&=:
The
&&=
operator performs the assignment only when the left operand is truthy.letx=10;lety=20;x&&=y;console.log(x);// 20
The above logical assignment operation can be expanded to:
x=x&&(x=y);(OR)if(x){x=y;}
||=:
The ||= operator performs the assignment only when the left operand is falsy.
letx=0;lety=20;x||=y;console.log(x);// 20
The above logical assignment operation can be expanded to:
x=x||(x=y);(OR)if(!x){x=y;}
??=:
The ??= operator performs the assignment only when the left operand is null or undefined.
letx;lety=1;x??=y;console.log(x);// 1
The above logical assignment operation can be expanded to:
x=x??(x=y);(OR)if(!x){x=y;}
About
ECMAScript features cheatsheet
Resources
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Languages
- JavaScript100.0%