Modern JavaScript: 7 ES6+ Features Every Developer Should Know

Published on: by Dr. Talib

If you learned JavaScript before 2015, you might remember a language full of quirks, callbacks, and the dreaded var keyword. But JavaScript has grown up.

Starting with ES6 (ECMAScript 2015) and continuing with yearly updates, Modern JavaScript is cleaner, more powerful, and easier to read. Here are the top features you need to be using today.

1. Let and Const (RIP Var)

The var keyword had a major flaw: it didn't respect block scope (like inside an if statement). This led to weird bugs.

  • const: Use this by default. It means the variable cannot be reassigned.
  • let: Use this only if you need to change the value later (like in a loop).
// Old
var name = "John";

// New
const name = "John"; // Can't change
let age = 30;        // Can change

2. Arrow Functions

Arrow functions provide a shorter syntax and, crucially, they don't bind their own this. This solves the classic "self = this" hack.

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

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

3. Template Literals

Stop using the plus sign to concatenate strings. Template literals allow you to embed variables directly into strings using backticks (`).

const name = "Alice";
// Old
console.log("Hello, " + name + "!");

// New
console.log(`Hello, ${name}!`);

4. Destructuring

Destructuring allows you to unpack values from arrays or properties from objects into distinct variables. It's incredibly useful for API responses.

const user = { name: "Bob", age: 25, city: "Paris" };

// Old
const name = user.name;
const age = user.age;

// New
const { name, age } = user;

5. Default Parameters

You can now define default values for function parameters directly in the function signature.

// Old
function greet(name) {
  name = name || "Guest";
  console.log("Hello " + name);
}

// New
const greet = (name = "Guest") => {
  console.log(`Hello ${name}`);
}

6. Async / Await

This is arguably the biggest game-changer. It allows you to write asynchronous code (like fetching data) that looks and behaves like synchronous code. No more "Callback Hell."

// Old (Promises)
fetch('/api/user')
  .then(response => response.json())
  .then(data => console.log(data));

// New (Async/Await)
async function getUser() {
  const response = await fetch('/api/user');
  const data = await response.json();
  console.log(data);
}

7. Modules (Import / Export)

JavaScript now has a native module system. You can split your code into multiple files and import them where needed, without relying on external tools like Webpack (though tools are still useful for bundling).

// math.js
export const add = (a, b) => a + b;

// main.js
import { add } from './math.js';
console.log(add(2, 3));

8. The Spread and Rest Operators (`...`)

The three dots (`...`) serve two distinct but related purposes depending on where they are used. Spread expands an iterable (like an array or object) into individual elements. Rest collects multiple elements and condenses them into a single array.

Spread Operator

It's perfect for copying arrays, combining objects, or passing array elements as individual arguments to a function.

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// Old: arr1.concat(arr2)
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

// Copying objects
const user = { name: "Alice", age: 30 };
const updatedUser = { ...user, location: "NY" };

Rest Parameters

Instead of relying on the confusing, array-like `arguments` object in functions, you can use Rest parameters to gather an indefinite number of arguments into a true Array.

function sumAll(...numbers) {
  // 'numbers' is a real array
  return numbers.reduce((acc, curr) => acc + curr, 0);
}
sumAll(1, 2, 3, 4); // Returns 10

9. Optional Chaining (`?.`)

This is a newer feature (ES2020) but it is absolutely essential for modern development, especially when dealing with deeply nested JSON data from an API where certain properties might be missing or `null`.

Instead of writing long, defensive `if` statements to check if an object exists before accessing its property, Optional Chaining short-circuits the expression and returns `undefined` safely.

const user = {
  id: 1,
  profile: {
    // Note: 'address' object is missing
  }
};

// Old: Uncaught TypeError: Cannot read properties of undefined
// const zip = user.profile.address.zipcode; 

// Old defensive approach
const zipOld = user && user.profile && user.profile.address ? user.profile.address.zipcode : undefined;

// New (Optional Chaining)
const zipNew = user?.profile?.address?.zipcode; // Safely returns undefined

10. Powerful Array Methods (`map`, `filter`, `reduce`)

While technically introduced in ES5, they became vastly more popular alongside ES6 Arrow Functions. Modern JavaScript developers rarely use standard `for` loops to iterate over data anymore. These declarative methods make intent much clearer.

  • map(): Transforms an array by applying a function to every item and returning a new array of the same length.
  • filter(): Evaluates every item against a condition and returns a new array containing only the items that passed.
  • reduce(): Reduces an array down to a single value (like a sum, or combining elements into an object).
const products = [
  { id: 1, name: 'Laptop', price: 999, active: true },
  { id: 2, name: 'Mouse', price: 25, active: false },
  { id: 3, name: 'Keyboard', price: 75, active: true }
];

// Get prices of active products only
const activePrices = products
  .filter(p => p.active)
  .map(p => p.price); // [999, 75]

Conclusion

These features aren't just "syntactic sugar." They make your code safer, more readable, and easier to maintain. If you're still writing "legacy" JavaScript, pick one of these features and start using it today.

Try It Out: Open our Live HTML Viewer, switch the language to JavaScript (or just use script tags), and try writing an async function. It works right in your browser!