JavaScript45 min readBeginner

Complete JavaScript ES6+ Guide

Master modern JavaScript with practical examples, async/await, destructuring, and more advanced ES6+ features.

Table of Contents

Introduction to ES6+

ECMAScript 6 (ES6), also known as ECMAScript 2015, introduced significant improvements to JavaScript. This guide covers the most important ES6+ features that every modern JavaScript developer should know.

ES6+ features make JavaScript code more readable, maintainable, and powerful. From better variable declarations to asynchronous programming improvements, these features are essential for modern web development.

Let, Const, and Block Scope

ES6 introduced let and const as alternatives to var, providing block scope and preventing common JavaScript pitfalls.

The Problem with var

// var has function scope, not block scope
function example() {
  if (true) {
    var x = 1;
  }
  console.log(x); // 1 - accessible outside the block!
}

// var declarations are hoisted
console.log(y); // undefined (not an error)
var y = 2;
javascript

Using let and const

// let has block scope
function example() {
  if (true) {
    let x = 1;
    const y = 2;
  }
  console.log(x); // ReferenceError: x is not defined
}

// const must be initialized and cannot be reassigned
const name = "John";
// name = "Jane"; // TypeError: Assignment to constant variable

// const objects/arrays can be mutated
const user = { name: "John" };
user.name = "Jane"; // This is allowed
user.age = 30;      // This is allowed
javascript

Arrow Functions

Arrow functions provide a shorter syntax for writing functions and handle the this keyword differently.

Basic Syntax

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

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

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

// No parameters
const greet = () => "Hello!";

// Multiple statements (need curly braces and return)
const processUser = (user) => {
  const processed = user.name.toUpperCase();
  return `Hello, ${processed}!`;
};
javascript

Arrow Functions and 'this'

// Traditional function - 'this' depends on how it's called
const obj = {
  name: "John",
  greet: function() {
    setTimeout(function() {
      console.log(`Hello, ${this.name}`); // 'this' is undefined or window
    }, 1000);
  }
};

// Arrow function - 'this' is inherited from enclosing scope
const obj2 = {
  name: "John",
  greet: function() {
    setTimeout(() => {
      console.log(`Hello, ${this.name}`); // 'this' refers to obj2
    }, 1000);
  }
};
javascript

Template Literals

Template literals provide an easy way to create strings with embedded expressions and multi-line strings.

// String concatenation (old way)
const name = "John";
const age = 30;
const message = "Hello, my name is " + name + " and I'm " + age + " years old.";

// Template literals (ES6)
const message2 = `Hello, my name is ${name} and I'm ${age} years old.`;

// Multi-line strings
const html = `
  <div class="user-card">
    <h2>${name}</h2>
    <p>Age: ${age}</p>
    <p>Status: ${age >= 18 ? 'Adult' : 'Minor'}</p>
  </div>
`;

// Function calls in templates
const formatPrice = (price) => `$${price.toFixed(2)}`;
const product = { name: "iPhone", price: 999.99 };
const productInfo = `${product.name} costs ${formatPrice(product.price)}`;
javascript

Destructuring

Destructuring allows you to extract values from arrays and objects into distinct variables.

Array Destructuring

// Traditional way
const numbers = [1, 2, 3, 4, 5];
const first = numbers[0];
const second = numbers[1];

// Destructuring
const [first, second, third] = numbers;
console.log(first, second, third); // 1, 2, 3

// Skip elements
const [, , third, , fifth] = numbers;

// Default values
const [a, b, c = 10] = [1, 2];
console.log(c); // 10

// Rest operator
const [head, ...tail] = numbers;
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
javascript

Object Destructuring

const user = {
  name: "John",
  age: 30,
  email: "john@example.com",
  address: {
    city: "New York",
    country: "USA"
  }
};

// Basic destructuring
const { name, age, email } = user;

// Rename variables
const { name: userName, age: userAge } = user;

// Default values
const { name, age, phone = "Not provided" } = user;

// Nested destructuring
const { address: { city, country } } = user;

// Function parameters
function greetUser({ name, age }) {
  return `Hello ${name}, you are ${age} years old.`;
}

greetUser(user);
javascript

Spread and Rest Operators

The spread operator (...) allows an iterable to expand in places where multiple elements are expected.

Spread Operator

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

// Object spreading
const user = { name: "John", age: 30 };
const updatedUser = { ...user, age: 31, city: "New York" };
// { name: "John", age: 31, city: "New York" }

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

// Copying arrays/objects
const originalArray = [1, 2, 3];
const copiedArray = [...originalArray];
const originalObject = { a: 1, b: 2 };
const copiedObject = { ...originalObject };
javascript

Rest Parameters

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

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

// Mixed parameters
function greet(greeting, ...names) {
  return `${greeting} ${names.join(", ")}!`;
}

console.log(greet("Hello", "John", "Jane", "Bob")); // "Hello John, Jane, Bob!"
javascript

Promises and Async/Await

Promises provide a cleaner way to handle asynchronous operations, and async/await makes asynchronous code look synchronous.

Creating and Using Promises

// Creating a Promise
function fetchUserData(userId) {
  return new Promise((resolve, reject) => {
    // Simulate API call
    setTimeout(() => {
      if (userId > 0) {
        resolve({ id: userId, name: "John", email: "john@example.com" });
      } else {
        reject(new Error("Invalid user ID"));
      }
    }, 1000);
  });
}

// Using Promises
fetchUserData(1)
  .then(user => {
    console.log("User:", user);
    return fetchUserPosts(user.id);
  })
  .then(posts => {
    console.log("Posts:", posts);
  })
  .catch(error => {
    console.error("Error:", error.message);
  });
javascript

Async/Await

// Using async/await
async function getUserData(userId) {
  try {
    const user = await fetchUserData(userId);
    const posts = await fetchUserPosts(user.id);
    
    return {
      user,
      posts
    };
  } catch (error) {
    console.error("Error:", error.message);
    throw error;
  }
}

// Calling async function
async function main() {
  try {
    const data = await getUserData(1);
    console.log("User data:", data);
  } catch (error) {
    console.error("Failed to get user data");
  }
}

// Promise.all with async/await
async function fetchMultipleUsers(userIds) {
  const promises = userIds.map(id => fetchUserData(id));
  const users = await Promise.all(promises);
  return users;
}
javascript

ES6 Modules

ES6 modules provide a standardized way to organize and share code between files.

Named Exports and Imports

// math.js - Named exports
export const PI = 3.14159;

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

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

// Or export all at once
const subtract = (a, b) => a - b;
const divide = (a, b) => a / b;

export { subtract, divide };
javascript
// main.js - Named imports
import { PI, add, multiply } from './math.js';

console.log(PI); // 3.14159
console.log(add(2, 3)); // 5

// Import with alias
import { subtract as sub, divide as div } from './math.js';

// Import all
import * as math from './math.js';
console.log(math.add(2, 3)); // 5
javascript

Default Exports

// user.js - Default export
export default class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
  
  greet() {
    return `Hello, I'm ${this.name}`;
  }
}

// main.js - Default import
import User from './user.js';

const user = new User("John", "john@example.com");
console.log(user.greet());
javascript

Classes

ES6 classes provide a cleaner syntax for creating constructor functions and handling inheritance.

// Basic class
class Animal {
  constructor(name, species) {
    this.name = name;
    this.species = species;
  }
  
  speak() {
    return `${this.name} makes a sound`;
  }
  
  // Static method
  static getInfo() {
    return "Animals are living organisms";
  }
  
  // Getter
  get description() {
    return `${this.name} is a ${this.species}`;
  }
  
  // Setter
  set nickname(nick) {
    this._nickname = nick;
  }
  
  get nickname() {
    return this._nickname || this.name;
  }
}

// Inheritance
class Dog extends Animal {
  constructor(name, breed) {
    super(name, "dog"); // Call parent constructor
    this.breed = breed;
  }
  
  speak() {
    return `${this.name} barks!`;
  }
  
  wagTail() {
    return `${this.name} wags tail happily`;
  }
}

// Usage
const myDog = new Dog("Buddy", "Golden Retriever");
console.log(myDog.speak()); // "Buddy barks!"
console.log(myDog.description); // "Buddy is a dog"
console.log(Animal.getInfo()); // "Animals are living organisms"
javascript

Conclusion

ES6+ features have revolutionized JavaScript development, making code more readable, maintainable, and powerful. These features are now standard in modern JavaScript development and are essential for any developer.

Key Takeaways

  • Use let and const instead of var
  • Arrow functions provide cleaner syntax and better this handling
  • Template literals make string formatting much easier
  • Destructuring simplifies extracting values from objects and arrays
  • Async/await makes asynchronous code more readable
  • ES6 modules provide a standard way to organize code
  • Classes offer a cleaner syntax for object-oriented programming

Practice these concepts in your daily coding, and you'll find your JavaScript code becoming more modern, efficient, and easier to maintain. Happy coding!