Modern JavaScript: Essential ES6+ Features


JavaScript has evolved significantly since ES6 (ES2015). If you learned JavaScript years ago or are still writing ES5-style code, these modern features will transform how you write JavaScript. Let’s explore the most important ones.

let and const

Say goodbye to var. Use let for variables that change and const for constants.

// Old way
var name = "Alice";
var age = 30;

// Modern way
let name = "Alice";  // Can be reassigned
const age = 30;      // Cannot be reassigned

Why it matters:

  • const prevents accidental reassignment
  • Block scoping prevents bugs
  • Makes code intent clearer

Arrow Functions

A concise syntax for writing functions:

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

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

// With multiple lines
const greet = (name) => {
  const message = `Hello, ${name}!`;
  return message;
};

// Single parameter (parentheses optional)
const double = x => x * 2;

Bonus: Arrow functions don’t create their own this context:

class Timer {
  constructor() {
    this.seconds = 0;
    setInterval(() => {
      this.seconds++;  // 'this' refers to Timer instance
    }, 1000);
  }
}

Template Literals

String interpolation and multiline strings:

// Old way
const name = "Alice";
const greeting = "Hello, " + name + "!";

// Template literals
const greeting = `Hello, ${name}!`;

// Multiline
const html = `
  <div>
    <h1>Welcome, ${name}</h1>
    <p>You have ${messages.length} new messages</p>
  </div>
`;

// Expressions
const price = `Total: $${(quantity * price).toFixed(2)}`;

Destructuring

Extract values from objects and arrays:

// Object destructuring
const user = { name: "Alice", age: 30, city: "NYC" };
const { name, age } = user;

// With renaming
const { name: userName, age: userAge } = user;

// With defaults
const { country = "USA" } = user;

// Array destructuring
const colors = ["red", "green", "blue"];
const [first, second] = colors;  // "red", "green"

// Skip elements
const [, , third] = colors;  // "blue"

// Rest operator
const [primary, ...others] = colors;  // "red", ["green", "blue"]

// Function parameters
function greet({ name, age }) {
  return `${name} is ${age} years old`;
}
greet(user);

Spread Operator

Expand arrays and objects:

// Array spreading
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];  // [1, 2, 3, 4, 5, 6]

// Copy array
const copy = [...arr1];

// Object spreading
const defaults = { theme: "dark", lang: "en" };
const userPrefs = { lang: "fr" };
const settings = { ...defaults, ...userPrefs };
// { theme: "dark", lang: "fr" }

// Function arguments
const numbers = [1, 2, 3];
Math.max(...numbers);  // 3

Rest Parameters

Collect multiple arguments into an array:

function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}

sum(1, 2, 3, 4);  // 10

// With other parameters
function multiply(multiplier, ...numbers) {
  return numbers.map(n => n * multiplier);
}

multiply(2, 1, 2, 3);  // [2, 4, 6]

Default Parameters

Provide default values for function parameters:

// Old way
function greet(name) {
  name = name || "Guest";
  return "Hello, " + name;
}

// Modern way
function greet(name = "Guest") {
  return `Hello, ${name}`;
}

// With expressions
function createUser(name, role = "user", id = generateId()) {
  return { name, role, id };
}

Object Shorthand

Concise object creation:

const name = "Alice";
const age = 30;

// Old way
const user = {
  name: name,
  age: age,
  greet: function() {
    return "Hello";
  }
};

// Shorthand
const user = {
  name,
  age,
  greet() {
    return "Hello";
  }
};

Promises

Handle async operations:

// Creating a promise
function fetchUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({ id, name: "Alice" });
      } else {
        reject("Invalid ID");
      }
    }, 1000);
  });
}

// Using promises
fetchUser(1)
  .then(user => console.log(user))
  .catch(error => console.error(error));

// Chaining
fetchUser(1)
  .then(user => fetchPosts(user.id))
  .then(posts => console.log(posts))
  .catch(error => console.error(error));

Async/Await

Cleaner async code:

// With promises (callback hell)
fetchUser(1)
  .then(user => {
    return fetchPosts(user.id);
  })
  .then(posts => {
    return fetchComments(posts[0].id);
  })
  .then(comments => {
    console.log(comments);
  });

// With async/await
async function loadUserData() {
  try {
    const user = await fetchUser(1);
    const posts = await fetchPosts(user.id);
    const comments = await fetchComments(posts[0].id);
    console.log(comments);
  } catch (error) {
    console.error(error);
  }
}

// Parallel requests
async function loadData() {
  const [users, posts] = await Promise.all([
    fetchUsers(),
    fetchPosts()
  ]);
}

Classes

Object-oriented programming in JavaScript:

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  greet() {
    return `Hello, I'm ${this.name}`;
  }

  // Getter
  get displayName() {
    return this.name.toUpperCase();
  }

  // Static method
  static create(data) {
    return new User(data.name, data.email);
  }
}

// Inheritance
class Admin extends User {
  constructor(name, email, permissions) {
    super(name, email);
    this.permissions = permissions;
  }

  greet() {
    return `${super.greet()} - I'm an admin`;
  }
}

Modules

Organize code into reusable modules:

// math.js
export const PI = 3.14159;

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

export default function multiply(a, b) {
  return a * b;
}

// main.js
import multiply, { add, PI } from './math.js';
// Or import everything
import * as Math from './math.js';

Array Methods

Powerful array manipulation:

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

// map: Transform each element
const doubled = numbers.map(n => n * 2);

// filter: Keep elements that pass test
const evens = numbers.filter(n => n % 2 === 0);

// reduce: Combine into single value
const sum = numbers.reduce((total, n) => total + n, 0);

// find: Get first matching element
const first = numbers.find(n => n > 3);  // 4

// some: Check if any pass test
const hasEven = numbers.some(n => n % 2 === 0);  // true

// every: Check if all pass test
const allPositive = numbers.every(n => n > 0);  // true

// includes: Check if value exists
const hasThree = numbers.includes(3);  // true

// Chaining
const result = numbers
  .filter(n => n % 2 === 0)
  .map(n => n * 2)
  .reduce((sum, n) => sum + n, 0);

Optional Chaining

Safely access nested properties:

const user = {
  name: "Alice",
  address: {
    city: "NYC"
  }
};

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

// Optional chaining
const zip = user?.address?.zip;  // undefined (no error)

// With arrays
const firstPost = user?.posts?.[0];

// With functions
const result = obj.method?.();  // Only call if method exists

Nullish Coalescing

Default values that only trigger on null/undefined:

// Old way (problematic with falsy values)
const count = userInput || 10;  // 10 if userInput is 0, "", false

// Nullish coalescing (only null/undefined)
const count = userInput ?? 10;  // 0 if userInput is 0

Object Methods

// Object.entries()
const user = { name: "Alice", age: 30 };
Object.entries(user);  // [["name", "Alice"], ["age", 30]]

// Object.keys()
Object.keys(user);  // ["name", "age"]

// Object.values()
Object.values(user);  // ["Alice", 30]

// Object.fromEntries()
const entries = [["name", "Bob"], ["age", 25]];
Object.fromEntries(entries);  // { name: "Bob", age: 25 }

Start Using These Today

You don’t need to learn everything at once. Start with:

  1. let/const - Use instead of var
  2. Arrow functions - For cleaner callbacks
  3. Template literals - For strings
  4. Destructuring - Extract values easily
  5. async/await - For async operations

These features are supported in all modern browsers and Node.js. For older browsers, use Babel to transpile your code.

Resources

Modern JavaScript makes code more readable, maintainable, and powerful. Start incorporating these features into your projects today!