Skip to main content

ES6 and beyond

Greetings!

JavaScript got a boost with ES6 and new ECMAScript specifications. However, as we are used to old ways, it might feel weird to use these new features and we will miss them as well. This is a list of useful features without lengthy explanations.

What to expect

  • let, const over var
  • Arrow functions
  • for .. of a new loop
  • Promise promises no callback hell
  • async, await further simplifies the promise
  • Template literals
  • Default values
  • Shorthand assignment
  • Rest parameter
  • Spread operator
  • Destructuring
  • class
  • map, set
  • Modules
  • Nullability

let, const over var

We are used to 'var' so that we forget the problems that come with it. ES6 introduces let and const to overcome those issues. It provides block scoping that was missing in 'var'.
var name = 'Jon';
// now
let firstName = 'Jon';
const another = 'You can not change this';

Arrow functions

We are using the function syntax from the beginning but arrow functions make it nicer. However, be aware of 'this' as the arrow function provides consistent scope compared to normal functions.
// then
setTimeout(function() {
  console.log('greetings');
}, 2000);
// now
setTimeout(() => console.log('greetings'), 2000);

for .. of

We have for .. in the loop for objects.
const user = {
  name: 'Manjula',
  age: 30
};

for (const u in user) {
  console.log(u);
}
Same as above, now we have a "for .. of" loop for arrays.
const names = ['Sara', 'Jake', 'Pete', 'Mark', 'Jill'];
// then
for (var i = 0; i < names.length; i++) {
  console.log(names[i]);
}
// now
for (const name of names) {
  console.log(name);
}

for (const name of names.entries()) {
  console.log(name);
}

for (const [i, name] of names.entries()) {
  console.log(`${i}, ${name}`);
}

Promise promises no callback hell

JavaScript is an asynchronous programming language. Hence we don't get the result immediately. To get our results back, we use(d) callbacks and end up with callback hell.
function getUserCommits(id, callback) {
  getUser(id, function(user) {
    getCommits(user.username, function(commits) {
	  callback(commits);
	});
  });
}

function getUser(userId, callback) {
  setTimeout(() => {
	callback({ id: userId, username: 'AName' });
  }, 1000);
}

function getCommits(username, callback) {
  setTimeout(() => {
	callback([ 'commit-1', 'commit-2']);
  }, 1000);
}

getUserCommits(1, function(commits) {
  console.log(commits);
});
This can be simplified with Promises and saying no to callback hell. The promise gives us a 'thenable' chain where we access the async results in 'then' methods. This gives us a little synchronous feeling.
function getUserCommits(id) {
  return getUser(id).then(user => getCommits(user.username));
}

function getUser(userId) {
  return new Promise((resolve) => {
    setTimeout(() => {
	  resolve({ id: userId, username: 'AName' });
	}, 1000);
  });
}

function getCommits(username) {
  return new Promise((resolve) => {
    setTimeout(() => {
	  resolve([ 'commit-1', 'commit-2']);
	}, 1000);
  });
}

async, await further simplifies the promise

Promises are great but still, we have to turn our heads around to understand thenables. async/await simplifies this further by giving us the proper synchronous way of coding. Be aware that this is actually a promise in disguise and this returns a promise.
async function getUserCommits(id) {
  const user = await getUser(id);
  const commits = await getCommits(user.username);
  return commits;
}

getUserCommits(1).then(commits => console.log(commits));

Template literals

Say no to string concatenation with template literals.
const firtName = 'John';
const lastName = 'Doe';
// then
console.log('Hello, ' + firtName + ' ' + lastName);
// now
console.log(`Hello, ${firtName} ${lastName}`);
We can even have multiline text without plus (+).
const name = 'John Doe';
const message = `Dear ${name},
This is
a multi line text.
`;
console.log(message);

Default values in functions

When the caller of the function does not provide values we had to check it and provide default values previously. Now, this is simplified by giving default values in the function itself.
function greet(name, message = 'Hello World') {
  console.log(`${name}, ${message}`);
}
greet('Manjula');
greet('Manjula', 'Hi');

Shorthand assignment

When we create objects in a function with parameters (a factory), we had to repeat parameter names.
const createPerson = function (name, age) {
  return {
    name: name,
    age: age,
    toString: function () {
      return this.name + ' ' + this.age;
    }
  }
}
Instead, now we can write the above code as follow. Note that for functions also we can omit functions and add only the method name.
// now
const createPerson = function (name, age) {
  return {
    name,
    age,
    toString() {
      return `${this.name} ${this.age}`;
    }
  }
}

Rest parameter

We used 'arguments' to accept multiple parameters. However, it is error-prone as it is not an array. We can now use the rest operator for this. (three dots before the parameter).
// then
function max1() {
  console.log(arguments instanceof Array);
  let large = arguments[0];
  for (let i = 0; i < arguments.length; i++) {
    if (large < arguments[i]) {
      large = arguments[i];
    }
  }
  return large;
}
// now
function max2(...values) {
  console.log(values instanceof Array);
  return values.reduce((large, value) => large > value ? large : value, values[0]);
}

console.log(max2(1, 2, 5, 6, 2, 7, 3, 9));

Spread operator

Just like the name, it spreads values. This is very helpful to construct arrays, and objects using existing arrays, and objects.

Previously, we had to assign elements by index. Instead, now we can spread it.
const names1 = ['Tom', 'Jerry'];
const names2 = ['Butch', 'Spike', 'Tyke'];
const names3 = [ ...names1, ...names2 ];
console.log(names3); // [ 'Tom', 'Jerry', 'Butch', 'Spike', 'Tyke' ]

function greet(name, message) {
  console.log(`Greeting ${name} with ${message}`);
}

const greets = ['Manjula', 'Hello World'];
greet(...greets);
This can be used with objects as well.
const sam = {
  name: 'Sam',
  age: 2
};

const sam1 = {...sam };

const sam2 = {
  ...sam,
  age: 3
};

const sam3 = {
  ...sam,
  age: 4,
  height: 100
};

console.log(sam, sam1, sam2, sam3,);

Array destructuring

When we want to get specific elements from an array we had to get them via the index.
const countries = [ 'Sri Lanka', 'India', 'Pakistan' ];
// then
const sriLanka = countries[0];
const india = countries[1];
const pakistan = countries[2];
// now
const [ sriLanka, india, pakistan ] = countries;

function printName([ firstName, lastName ]) {
  console.log(`${firstName} ${lastName}`);
}
printName([ 'Dasun', 'Shanaka' ]);

Object destructuring

Same as arrays, when we want to get specific values from an object, we had to get them one by one.
const person = {
  firstName: 'Dasun',
  lastName: 'Shanaka',
  age: 15,
  sex: 'male'
};
// then
const firstName = person.firstName;
const age = person.age;
// now
const { firstName, age, sex: gender } = person;
console.log(`${firstName}, ${age}, ${gender}`);

function printPerson({ firstName, age }) {
  console.log(`${firstName} is ${age} years old`);
}
printPerson(person);

class hides prototype

ES6 offers a nicer way to create classes in JavaScript. However, this is just syntactic sugar as underlined it is just a prototype.
// then
const Car = function(year) {
  this.year = year;

  this.turn = function(direction) {
    console.log('...turning...');
  }
}
const car1 = new Car(2018);
// now
class Car {
  constructor(year) {
    this.year = year;
  }

  turn(direction) {
    console.log(`...turning ${direction}...`);
  }
}
cost car2 = new Car(2018);
console.log(car2);
console.log(car2.turn('left'));

Creating class properties with get, and set

We can add computed properties with get and set.
class Car {
  constructor(year) {
    this.year = year;
    this.miles = 0;
  }

  get distanceTraveled() {
    return this.miles;
  }

  set distanceTraveled(value) {
    if (value < this.miles) {
      throw new Error(`Sorry, can't set value to less than current distance traveled.`);
    }
    this.miles = value;
  }
}
const car = new Car(2019);
car.distanceTraveled = 10;
console.log(`distance travelled ${car.distanceTraveled}`);

Defining class members with static

We can create member variables and methods with 'static'.
class Car {
  constructor(year) {
    this.year = year;
  }

  static get ageFactor() {
    return 0.1;
  }

  static pickLatest(carA, carB) {
    return carA.year > carB ? carA : carB;
  }
}
const carA = new Car(2019);
const carB = new Car(2022);
console.log(`Age factor is ${Car.ageFactor}`);
console.log(`Latest is ${Car.pickLatest(carA, carB).year}`);

Set, Map

Set can be used to create collections with unique values.
const names = new Set(['Jack', 'Jill', 'Jake', 'Jack', 'Sara']);
console.log(names.size);
names.add('Mike');
However, Set does not come with a proper way to iterate. We can fix this by converting it to an array using the spread operator.
[...names].filter(name => name.startsWith('J'))
  .map(name => name.toUpperCase())
  .forEach(name => console.log(name));
Map can be used to create dictionaries.
const scores = new Map([['Sara', 12], ['Bob', 11], ['Jill', 15], ['Bruce', 14]]);
scores.set('Jake', 14);
console.log(scores.size);

for(const [name, score] of scores.entries()) {
  console.log(`${name} : ${score}`);
}

scores.forEach((score, name) => console.log(`${name} : ${score}`));

Modules

Here comes the JavaScript module system where we can enjoy file based scoping. We "export" our variables, and functions when we want to expose them and "import" to use.
// file1.js
export const left = function () {
  console.log('left called');
};

const country = 'Sri Lanka';
export default country;
// index.js
import country, { left } from './file1.js';

left();

console.log(country);

Nullability

Using the || operator is very common to assign a default value when the one you are attempting to assign is null or undefined.
const size = books.size || 10;
To overcome potential errors that come with this approach nullish coalescing operator (??) can be used.
// now
const size = books.size ?? 10;
Accessing properties that can be null, or undefined leads to writing codes with multiple if conditions.
// then
const name = txtName ? txtName.value : undefined;
The optional chaining operator (?.) allows you to have a more compact and readable code.
// now
const name = txtName?.value;
We even can chain it.
const customerCity = purchase?.customer?.address?.city;
const fullName = user.getFullName?.();

Conclusion

Congratulations if you have come so far. This covers ES6+ necessary features in brief. However, you need to learn them in more detail. Use the below references as needed.

References

https://developer.mozilla.org/en-US/docs/Web/JavaScript
Rediscovering JavaScript MasterES6, ES7, and ES8

Happy learning ☺















Comments