Modern JavaScript Features (ES6+)

ECMAScript 2015 (ES6) and subsequent versions introduced many powerful features that transformed JavaScript development. These modern features make code more concise, readable, and maintainable while providing new capabilities for solving complex problems.

Introduction to ECMAScript Versions

ECMAScript is the standardized specification for JavaScript. Since 2015, new versions have been released annually, each adding new features to the language.

Version Year Key Features
ES6 / ES2015 2015 Arrow functions, classes, template literals, destructuring, let/const, promises, modules
ES2016 2016 Array.includes(), exponentiation operator (**)
ES2017 2017 Async/await, Object.entries(), Object.values(), string padding
ES2018 2018 Rest/spread for objects, async iteration, Promise.finally()
ES2019 2019 Array.flat(), Array.flatMap(), Object.fromEntries(), String.trimStart()/trimEnd()
ES2020 2020 Optional chaining, nullish coalescing, BigInt, Promise.allSettled()
ES2021 2021 String.replaceAll(), Promise.any(), logical assignment operators
ES2022 2022 Class fields, top-level await, Array.at(), Object.hasOwn()

ES6 (ES2015) Features

ES6

Arrow Functions

Arrow functions provide a concise syntax for writing functions and lexically bind the this value.

// Traditional function
function add(a, b) {
  return a + b;
}

// Arrow function
const add = (a, b) => a + b;

// Arrow function with block body
const add = (a, b) => {
  const sum = a + b;
  return sum;
};

// Arrow function with single parameter (parentheses optional)
const double = x => x * 2;

// Arrow function with no parameters
const getRandomNumber = () => Math.random();

Note: Arrow functions don't have their own this context. They inherit this from the enclosing scope, which can be useful in callbacks but problematic in object methods.

ES6

let and const Declarations

Block-scoped variable declarations that replace var for most use cases.

// let - block-scoped variable that can be reassigned
let count = 1;
count = 2; // Valid

// const - block-scoped variable that cannot be reassigned
const PI = 3.14159;
// PI = 3.14; // Error: Assignment to constant variable

// Block scope
if (true) {
  let blockScoped = 'only available in this block';
  const alsoBlockScoped = 'also only in this block';
  var notBlockScoped = 'available outside the block';
}
// console.log(blockScoped); // ReferenceError
// console.log(alsoBlockScoped); // ReferenceError
console.log(notBlockScoped); // Works

Tip: Use const by default, and only use let when you need to reassign a variable. This makes your code more predictable and easier to reason about.

ES6

Template Literals

String literals that allow embedded expressions and multi-line strings.

// String concatenation (old way)
const name = 'John';
const greeting = 'Hello, ' + name + '!';

// Template literals (new way)
const greeting = `Hello, ${name}!`;

// Multi-line strings
const multiLine = `This is a string
that spans multiple
lines without needing \n`;

// Expressions in template literals
const a = 5;
const b = 10;
console.log(`The sum of ${a} and ${b} is ${a + b}`);
ES6

Destructuring Assignment

Extract values from arrays or objects into distinct variables.

// Array destructuring
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]

// Object destructuring
const person = {
  name: 'John',
  age: 30,
  city: 'New York'
};

const { name, age } = person;
console.log(name, age); // 'John', 30

// Renaming variables
const { name: fullName, age: years } = person;
console.log(fullName, years); // 'John', 30

// Default values
const { name, job = 'Unknown' } = person;
console.log(job); // 'Unknown'
ES6

Rest and Spread Operators

Collect multiple elements into an array (rest) or expand an array into individual elements (spread).

// Rest operator in function parameters
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

// Spread operator with arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// Spread operator to clone an array
const original = [1, 2, 3];
const copy = [...original];

Note: In ES2018, the rest and spread operators were extended to work with objects as well.

ES6

Classes

Syntactic sugar over JavaScript's prototype-based inheritance.

// ES6 Class
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  greet() {
    return `Hello, my name is ${this.name}`;
  }
  
  static createAnonymous() {
    return new Person('Anonymous', 0);
  }
}

const john = new Person('John', 30);
console.log(john.greet()); // "Hello, my name is John"

// Inheritance
class Employee extends Person {
  constructor(name, age, jobTitle) {
    super(name, age); // Call parent constructor
    this.jobTitle = jobTitle;
  }
  
  greet() {
    return `${super.greet()} and I work as a ${this.jobTitle}`;
  }
}
ES6

Promises

A built-in way to handle asynchronous operations.

// Creating a promise
const promise = new Promise((resolve, reject) => {
  // Asynchronous operation
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('Operation successful');
    } else {
      reject('Operation failed');
    }
  }, 1000);
});

// Using a promise
promise
  .then(result => {
    console.log(result); // "Operation successful"
    return 'Next step';
  })
  .then(result => {
    console.log(result); // "Next step"
  })
  .catch(error => {
    console.error(error);
  });

ES2016-ES2022 Features

ES2016

Array.prototype.includes()

Check if an array includes a certain element.

const numbers = [1, 2, 3, 4, 5];

// Old way
console.log(numbers.indexOf(3) !== -1); // true

// ES2016 way
console.log(numbers.includes(3)); // true
console.log(numbers.includes(6)); // false
ES2017

Async/Await

Syntactic sugar for working with promises, making asynchronous code look more like synchronous code.

// Promise-based approach
function fetchData() {
  return fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => {
      console.log(data);
      return data;
    })
    .catch(error => {
      console.error('Error:', error);
      throw error;
    });
}

// Async/await approach
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
    return data;
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}
ES2020

Optional Chaining

Safely access nested object properties without checking if each level exists.

const user = {
  name: 'John',
  address: {
    street: '123 Main St',
    city: 'New York'
  }
};

// Old way
const city = user && user.address && user.address.city;

// ES2020 way
const city = user?.address?.city;

// Works with function calls too
const result = user.getDetails?.();

// Works with array elements
const firstItem = array?.[0];
ES2020

Nullish Coalescing Operator

Provides a default value only when the left-hand side is null or undefined.

// Old way with logical OR
// Problem: returns fallback for all falsy values (0, '', false, null, undefined)
const count = data.count || 0;

// ES2020 way with nullish coalescing
// Only returns fallback for null or undefined
const count = data.count ?? 0;

// Examples
console.log(0 ?? 42);        // 0 (0 is not nullish)
console.log('' ?? 'default'); // '' (empty string is not nullish)
console.log(false ?? true);   // false (false is not nullish)
console.log(null ?? 42);      // 42 (null is nullish)
console.log(undefined ?? 42); // 42 (undefined is nullish)
ES2022

Top-level await

Use await outside of async functions in modules.

// In a module, you can now use await at the top level
// Without wrapping it in an async function

// ES2022
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);

// Export the data for other modules to use
export { data };

Next Steps

Now that you understand modern JavaScript features, you can explore:

  • JavaScript modules and module bundlers
  • Advanced patterns with modern JavaScript
  • Functional programming in JavaScript
  • TypeScript - a typed superset of JavaScript
  • Modern JavaScript frameworks that leverage these features