Sambat Lim Blog

moon indicating dark mode
sun indicating light mode

What new in each version of Modern Javascript - ECMAScript(ES) from ES6 - ES11🔥

May 16, 2020

If I needed to choose only one programming language to master, Javascript would be my choice. With Javascript we are able to create everything from web applications, mobile apps, and server-side applications, even desktop applications using Electron.js. I think this should convince you to learn Javascript! In order to get started with Javascript you should know some of its history - that’s what this blog is for. At the time I write this blog post, Javascript is on version 😍ES2020 (ES11)😍 already (ES is short for EcmaScript, the technical name for Javascript).

A brief overview of what is new in all ES versions:

  • ES2015 (ES6)
  • ES2016 (ES7)
  • ES2017 (ES8)
  • ES2018 (ES9)
  • ES2019 (ES10)
  • ES2020 (ES11)

ES2015 (ES6)

In 2015, Javascript was back in the game after it had been ignored by most serious programmers (see the Why is Javascript bad? discussion on quora). 2015 was the golden year of Javascript and had its biggest update of all time.

These are the main updates of ES2015:

  • Let/Const Variable Declaration
  • Arrow Functions
  • Classes
  • Modules (import/export)
  • Template Strings
  • Default function parameters
  • Rest/Spread Operators
  • Array/Object Destructuring
  • Promises
  • Iterators and Generators

Let/Const

let and const variable declarations were introduced. It is highly recommended to use them over var to be explicit about the nature of the variable.

let message = 'hello';
message = 'good bye'; // works just fine because let declares variables that can be altered
const message = 'hello';
message ='good bye'; // returns an error because the value of the Constant message cannot be changed

Arrow functions

With the old way, we would write functions as below:

function sum(value1, value2){
return value1 + value2;
}

But with arrow function we can write in a compressed way, like this:

const sum = (value1, value2)=>{
return value1 + value2;
}

The arrow syntax ()=>{} creates a function and the constant sum is declared to store it. This is also an example of an anonymous function because it has no explicit name, but is immediately assigned to a variable.

We can use an even more simple syntax if the operation we are performing immediately returns a value:

const sum = (value1, value2) => value1 + value2

Classes

In ES6 we are able to write Javascript in with Object Oriented Programming (OOP) principles.

For example, we create new class called Calculator (convention encourages the use of a capital letter when creating a class, but it isn’t required). The Calculator class constructor is called whenever the new keyword is used to make an instance of the class, and expects one parameter called moneyBeforeDiscount.

class Calculator{
constructor(moneyBeforeDiscount){
this.moneyBeforeDiscount = moneyBeforeDiscount;
}
moneyAfterDiscount(discountRate){
return this.moneyBeforeDiscount * (100-discountRate)/100;
}
}

We instantiate the Calculator class to a variable called bill and call one of its method to print out the total after discount.

const bill = new Calculator(5000);
console.log('Total after discount :' + bill.moneyAfterDiscount(5));

We can also use class inheritance with the keyword extend and reference back to parent class with the keyword super.

class MoreCalculate extend Calculator{
constructor(moneyBeforeDiscount,discountRate,moneyExchange){
super(moneyBeforeDiscount);
this.moneyExchange = moneyExchange;
this.discountRate = discountRate;
}
moneyAfterExchange(){
return this.moneyAfterDiscount(this.discountRate)/this.moneyExchange;
}
}

Modules (import/export)

Before ES6, Javascript didn’t support module management natively. In ES6 though, we can export functions and variables from a file, and then import those elements in a different file.

// calculator.js
const add = (a, b) => a + b
const multiply = (a, b) => a * b
export { add, multiply }
// App.js
import { add } from './calculator'
console.log(add(2, 3)); //5

Template Strings

Declaring strings is often done on one line using either single quotes ' or double quotes ". The backtick character ` allows us to create template strings and replace repetative + string operations to concatenate regular strings and variables as well as allow multiline string variables. In order to interpolate variables inline just wrap the variable name in ${}:

const sum = 2+5;
console.log(`2 + 5 = ${sum}`);
//normal method
console.log('2 + 5 ='+ sum);

You can also call functions and perform operations inside the template wrapper:

const message = 'khmer coder';
console.log(`good morning, ${message} and ${2+2}`);
//good morning, khmer coder and 4;

Default function parameter values

ES6 also includes the ability to set a default parameter value which allows developers to set a fallback value if a function is called without necessary parameters.

const greeting = (name, message = 'Hello') => console.log(`${message}, ${name}`);
greeting('Sambat'); // "Hello, Sambat"
greeting('khmer coder','Hi'); // "Hi, khmer coder"

Rest/Spread Operators

The Rest Operator (not related to REST API models) is used when there is an unknown or unlimited number of parameters in calling a function. By declaring ... before a variable name in a function definition it will create an iterable variable of any length:

function add(...numbers) {
sum = 0
for(const n of numbers) {
sum += n
}
return sum
}
console.log(add(1, 2, 3)) // 6

The Spread Operator is used to “spread out” an array by returning the individual values in order by entering ... before a variable. One use of this is to insert one array into the center of another, like:

const a = [4, 5, 6]
const b = [1, 2, 3, ...a, 7] // [1, 2, 3, 4, 5, 6, 7]

Array/Object Destructuring

Array and Object Destructuring are used when we want to assign the value of variables from somewhere in an array or object directly. When destructuring, the variables on the left of the assignment operator are updated but the Array or Object is left unchanged.

Array destructuring uses the order of the array to return a value and assigns it to the variable in the same position on the left side of the assignment operator.

const studentScore = [50, 25, 79]
const [first, second] = studentScore
console.log(first, second) // 50, 25

Object destructuring matches the variable names matched to the Object’s properties to perform the assignment.

const studentA = {
name: 'A',
score: 99
}
const { name, score } = studentA
console.log(`${name}: ${score}`) // A: 99

Promises

Promises are widely used in asynchronous programming to handle HTTP requests and other long running processes. Promises return a result with to a function declared with the .then method, and .thens can be chained to perform multiple process steps. Error handling is done with the .catch method calling its own function. All Promise handler functions can be declared anonymously inline.

url = 'https://api.github.com/search/users?q=sambatlim'
fetch(url)
.then(response => response.json())
.then(result => {
console.log(result)
})

Iterators and Generators

Iterators:
ES6 provided the new looping syntax for x of y, which is similar to foreach in most programming languages.

let arr= [a,b,c];
for(let value of arr){
console.log(value);
}
//output: a, b, c

Generators:
Generators can return (yield) multiple values, one after another, on demand. They work great with Iterators. To create the generator we need the special syntax construct: function* to explicity declare a normal function a generator. To get a value we need to use the generator method next(). next() will always return an Object with two properties:

  • value: the value that is yielded or returned
  • done: true if the function code has finished, otherwise false

When It reached the return statement, the function will finish and return done:true.

function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
let one = generator.next();
console.log(JSON.stringify(one));
//output: {value: 1, done: false}
let two = generator.next();
console.log(JSON.stringify(two));
//output: {value: 2, done: false}
let three = generator.next();
console.log(JSON.stringify(three));
//output: {value: 3, done: true}
//function has finished because it reached the return statement.

read more about generators


ES2016 (ES7)

ES2016 introduced the minor update from the ES6 such as the Array method .includes() to check for the presence of a value, and an exponential operator **.

Array.includes()

const number = [1,2,3]
console.log(number.includes(1)); //true
console.log(number.includes(7)); //false

Exponential Operator (**)

console.log(2 ** 3) // 8

ES2017 (ES8)

ES8 helps us to write the asynchronous functions in a more convenient way by introducing the keywords async/await. We can use these to avoid callback hell (a chain of dependant callback functions that can be very hard to debug) and improves the readability of Promises.

Async Functions (async/await)

With ES6 Promise handling we would write

fetch('https://api.github.com/search/users?q=sambatlim')
.then(response => {
// callback 1
response.json()
)
.then(result => {
// callback 2
console.log(result)
})

With ES2017 we can declare an overall function as async, and then explicilty use await within it to resolve Promise values without blocking the whole Javascript runtime, but maintaining the normal way to write and read a Javascript function.

async function getgithubprofile(){
const response = await fetch('https://api.github.com/search/users?q=sambatlim') // response isn't assigned until fetch returns
const result = await response.json() // result is not assigned until response is declared and json() returns a value
console.log(result)
}

recommend to read more here: article by Javascript info.


ES2018 (ES9)

In this update, ES2018 introduced nothing new but increased the ability of the Rest and Spread Operators (...) that were introduced in ES6. Again, the original Object is unaltered.

Rest Operator for Objects

Now we can use rest on the properties of an Object. It allows us to explicitly extract certain named variables, and assign any uncalled variables into a catchall Object.

const options = {
enabled: true,
text: 'Hello',
color: 'red'
}
const { enabled, ...others } = options
console.log(enabled) // true
console.log(others) // { text: 'Hello', color: 'red' }

Spread Operator for Objects

Using the spread operator during an Object declaration will assign the properties of the referenced Object to the new Object.

const moreOptions = {
text: 'Hello',
color: 'red'
}
const options = { enabled: true, ...moreOptions }
console.log(options) // { enabled: true, text: 'Hello', color: 'red' }

ES2019 (ES10)

An update for developer convience allows the use of try/catch without an explicit e Error reference in the catch call.

// Before
try {
fetch('https://example.com')
} catch (e) {
console.log(`e`)
}
// After
try {
fetch('https://example.com')
} catch {
console.log(`something wrong.`)
}

🔥ES2020 (ES11)

ES2020 introduced:

  • Dynamic Import
  • BigInt
  • Nullish Coalescing Operator(??)
  • Optional Chaining (?.)

Dynamic Import

In ES6 when we want to import references we need to write import at the beginning of the file. This is a static import and it cannot be conditional. With dynamic imports we can easily assign what modules we need to import on demand, which is very useful for big applications that can have a lot of modules.

let stageId = 'A'
let stage
if (stageId === 'A') {
stage = await import('./stages/A')
} else {
stage = await import('./stages/B')
}
stage.run()

read more at: v8.dev.

BigInt

BigInt is a Javascript data type that can store numbers larger than 253 - 1. To create a BigInt you append an n to the actual number.

const big = 123456789n

or convert to a BigInt by calling BigInt(yourNumber).

const alsoBig = BigInt(20)

Warning: The operators +, -, *, / can only be used on combinations of BigInt variables.

Nullish Coalescing Operator (??)

Before introducing you to the Nullish Coalescing Operator, lets talk about the way you write code to check for null or undefined values. If we wanted to check the value of score for example, you would usually write:

let score = null
console.log(score || 'No score') //this code will print 'No score'

The problem with this is it will check not only for undeclared values like null or undefined, but for “falsy” values like Boolean false, 0, "" (empty string), or NaN (Not a Number) and treat them as empty values as well:

let score = 0
console.log(score || 'No score') // this will print 'No score' instead of 0;

The introduction of the nullish coalescing operator ?? has come to help! It will evaluate the left hand argument strictly for null or undefined, allowing “falsy” values to be returned.

let score = 0
console.log(score ?? 'No score') //will print 0;

Optional Chaining (?.)

Let’s imagine you have an Object nested within another Object:

const student ={
profile:{
school:{
name:'RUPP'
}
}
}

If you want to get the name property of the student’s school property, you would normally use student.profile.school.name. But what if student.profile hasn’t been declared? You would get an error:

Uncaught TypeError: Cannot read property 'school' of undefined

So to prevent that error you could add an IF statement that checked for the existence of student.profile before trying to reference its properties, so that it returns before erroring out if profile is undefined:

if(student.profile && student.profile.school.name) {
return student.profile.school.name;
}

But that is long, and you should really check each level of property. Using the optional chaining syntax ?. on the profile property you can write the equivalent line:

student.profile?.school.name

This will prevent the error caused by an undeclared Object property.


🎉🎉That’s all for What’s New in Javascript! See you next year…🎉🎉


Welcome to Sambat Blog.
I blog about web development and design.