JavaScript ES6+ Modern Features

JavaScript ES6+ Modern Features

JavaScript has evolved significantly with ES6 (ECMAScript 2015) and later versions. These modern features make JavaScript more powerful and easier to work with.

What is ES6+?

ES6 (ECMAScript 2015) was a major update to JavaScript that introduced many new features. Since then, JavaScript gets annual updates (ES2016, ES2017, ES2018, etc.) with even more capabilities.

Why Modern JavaScript Matters

  • Better Code: Write cleaner, more readable code
  • Industry Standard: Most companies expect modern JS knowledge
  • Framework Requirements: React, Vue, Angular require modern JS
  • Future-Proof: Stay current with web development

Variable Declarations

let and const

Replace var with better alternatives:

// Old way (avoid)
var name = "John";
name = "Jane"; // Can be reassigned unexpectedly
var hobbies = ["coding", "reading"];

// Modern way (preferred)
let name = "John"; // Can be reassigned
const birthYear = 1990; // Cannot be reassigned
const hobbies = ["coding", "reading"]; // Array itself is const

// Block scope demonstration
if (true) {
    let blockVar = "I exist only here";
    const blockConst = "Me too";
}

console.log(blockVar); // ReferenceError: blockVar is not defined

Destructuring

Extract values from arrays and objects easily:

// Array destructuring
const colors = ["red", "green", "blue"];
const [first, second, third] = colors;
console.log(first); // red
console.log(third); // blue

// Skip values
const [primary, , secondary] = colors;
console.log(secondary); // green

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

const { name, age, city } = person;
console.log(name, age, city); // Alice 30 New York

// Default values and renaming
const { name: userName = "Guest", country = "USA" } = person;
console.log(userName, country); // Alice USA

Spread Operator

Copy and combine arrays/objects easily:

// Array spreading
const fruits = ["apple", "banana"];
const vegetables = ["carrot", "broccoli"];
const allFood = [...fruits, ...vegetables];
console.log(allFood); // ["apple", "banana", "carrot", "broccoli"]

// Copy array with new item
const newFruits = [...fruits, "orange"];
console.log(newFruits); // ["apple", "banana", "orange"]

// Object spreading
const basicUser = { name: "Bob", age: 25 };
const userDetails = { email: "bob@example.com", city: "Boston" };
const fullUser = { ...basicUser, ...userDetails };
console.log(fullUser);
// { name: "Bob", age: 25, email: "bob@example.com", city: "Boston" }

// Override properties
const updatedUser = { ...fullUser, age: 26 };
console.log(updatedUser);
// { name: "Bob", age: 26, email: "bob@example.com", city: "Boston" }

Arrow Functions

Basic Arrow Functions

Concise syntax for function expressions:

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

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

// Concise arrow function (single expression)
const add = (a, b) => a + b;

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

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

console.log(add(5, 3)); // 8
console.log(square(4)); // 16
console.log(greet()); // Hello, World!

Arrow Functions and this

Arrow functions don’t have their own this binding:

// Traditional function
const person = {
    name: "Alice",
    hobbies: ["coding", "reading"],
    greet: function() {
        console.log(`Hello, I'm ${this.name}`);
        this.hobbies.forEach(function(hobby) {
            console.log(`${this.name} likes ${hobby}`); // this is undefined
        });
    }
};

// Arrow function solution
person.greet = function() {
    console.log(`Hello, I'm ${this.name}`);
    this.hobbies.forEach(hobby => {
        console.log(`${this.name} likes ${hobby}`); // this refers to person
    });
};

person.greet();

Template Literals

String Interpolation

Create strings with variables and expressions:

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

// Modern way (template literals)
const message = `Hello, my name is ${name} and I'm ${age} years old.`;
console.log(message);

// Expressions in template literals
const price = 19.99;
const quantity = 3;
const total = `Total: $${(price * quantity).toFixed(2)}`;

// Multi-line strings
const html = `
    <div>
        <h1>Welcome</h1>
        <p>This is much cleaner than concatenation</p>
    </div>
`;

Tagged Template Literals

Custom processing of template literals:

function highlight(strings, ...values) {
    return strings.reduce((result, string, i) => {
        const value = values[i] ? `<mark>${values[i]}</mark>` : '';
        return result + string + value;
    }, '');
}

const name = "Alice";
const age = 30;
const highlighted = highlight`My name is ${name} and I'm ${age} years old.`;
console.log(highlighted);
// My name is <mark>Alice</mark> and I'm <mark>30</mark> years old.

Enhanced Object Methods

Property Shorthand

const name = "John";
const age = 30;

// Old way
const person = {
    name: name,
    age: age,
    greet: function() {
        return `Hello, I'm ${this.name}`;
    }
};

// Modern shorthand
const person = {
    name,        // Same as name: name
    age,         // Same as age: age
    greet() {    // Same as greet: function() {
        return `Hello, I'm ${this.name}`;
    }
};

console.log(person.greet()); // Hello, I'm John

Computed Property Names

const property = "dynamic";
const value = "I'm dynamic";

const obj = {
    [property]: value,        // Computed property name
    [`prefix_${property}`]: "Another value",
    [Date.now()]: "timestamp key"
};

console.log(obj.dynamic); // I'm dynamic

Object Methods

const person = {
    name: "Alice",
    age: 30
};

// Get keys
const keys = Object.keys(person);
console.log(keys); // ["name", "age"]

// Get values
const values = Object.values(person);
console.log(values); // ["Alice", 30]

// Get entries
const entries = Object.entries(person);
console.log(entries); // [["name", "Alice"], ["age", 30]]

// Object.assign (merging)
const details = { email: "alice@example.com", city: "New York" };
const merged = Object.assign({}, person, details);
console.log(merged);
// { name: "Alice", age: 30, email: "alice@example.com", city: "New York" }

// Spread operator (preferred)
const mergedModern = { ...person, ...details };
console.log(mergedModern);

Array Methods

Array.from()

Create arrays from array-like objects:

// From string
const name = "JavaScript";
const letters = Array.from(name);
console.log(letters); // ["J", "a", "v", "a", "S", "c", "r", "i", "p", "t"]

// From arguments
function sum() {
    return Array.from(arguments).reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

// With mapping function
const numbers = [1, 2, 3];
const doubled = Array.from(numbers, x => x * 2);
console.log(doubled); // [2, 4, 6]

Array.find() and Array.findIndex()

const users = [
    { id: 1, name: "Alice", age: 30 },
    { id: 2, name: "Bob", age: 25 },
    { id: 3, name: "Charlie", age: 35 }
];

// Find user by ID
const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: "Bob", age: 25 }

// Find index
const userIndex = users.findIndex(u => u.age > 30);
console.log(userIndex); // 2

Array.includes()

const fruits = ["apple", "banana", "orange"];
const hasApple = fruits.includes("apple");
const hasGrape = fruits.includes("grape");

console.log(hasApple); // true
console.log(hasGrape); // false

// With fromIndex
const hasOrange = fruits.includes("orange", 1); // Search from index 1
console.log(hasOrange); // true

Enhanced Function Parameters

Default Parameters

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

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

console.log(greet()); // Hello, Guest!
console.log(greet("Alice")); // Hello, Alice!

Rest Parameters

Handle unlimited arguments:

// Old way (arguments object)
function sum() {
    const args = Array.prototype.slice.call(arguments);
    return args.reduce((total, num) => total + num, 0);
}

// Modern way (rest parameters)
function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0);
}

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

// Mixed with regular parameters
function createUser(name, ...hobbies) {
    return {
        name,
        hobbies,
        hobbyCount: hobbies.length
    };
}

const user = createUser("Alice", "coding", "reading", "gaming");
console.log(user);
// { name: "Alice", hobbies: ["coding", "reading", "gaming"], hobbyCount: 3 }

Async/Await

Basic Async/Await

Modern way to handle asynchronous operations:

// Old way with promises
function fetchData() {
    return fetch('/api/data')
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => console.error(error));
}

// Modern way with async/await
async function fetchData() {
    try {
        const response = await fetch('/api/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
}

Multiple Async Operations

// Fetch data sequentially
async function fetchUserData() {
    const userResponse = await fetch('/api/user/1');
    const user = await userResponse.json();
    
    const postsResponse = await fetch(`/api/users/${user.id}/posts`);
    const posts = await postsResponse.json();
    
    return { user, posts };
}

// Fetch data in parallel
async function fetchParallel() {
    const [userResponse, postsResponse] = await Promise.all([
        fetch('/api/user/1'),
        fetch('/api/posts')
    ]);
    
    const user = await userResponse.json();
    const posts = await postsResponse.json();
    
    return { user, posts };
}

Async with Loops

const urls = [
    '/api/posts/1',
    '/api/posts/2',
    '/api/posts/3'
];

// Sequential processing
async function processSequentially() {
    for (const url of urls) {
        const response = await fetch(url);
        const data = await response.json();
        console.log('Processed:', data);
    }
}

// Parallel processing
async function processInParallel() {
    const promises = urls.map(url => fetch(url).then(res => res.json()));
    const results = await Promise.all(promises);
    results.forEach(data => console.log('Processed:', data));
}

Modules

Import/Export

Organize code into reusable modules:

// math.js - Export module
export const PI = 3.14159;

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

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

// Default export
export default function calculator(operation, a, b) {
    switch (operation) {
        case 'add': return add(a, b);
        case 'multiply': return multiply(a, b);
        default: return null;
    }
}

// main.js - Import module
import calculator, { add, multiply, PI } from './math.js';

console.log(add(5, 3)); // 8
console.log(multiply(4, 6)); // 24
console.log(PI); // 3.14159

const result = calculator('add', 10, 20);
console.log(result); // 30

Dynamic Imports

Load modules on demand:

// Load module only when needed
async function loadMath() {
    if (needMath) {
        const math = await import('./math.js');
        return math.add(5, 3);
    }
    return null;
}

// Default export with dynamic import
async function loadDefault() {
    const calculator = await import('./math.js');
    return calculator.default('multiply', 4, 5);
}

Classes

Class Syntax

class Person {
    // Constructor
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    // Method
    greet() {
        return `Hello, I'm ${this.name} and I'm ${this.age} years old.`;
    }

    // Getter
    get birthYear() {
        const currentYear = new Date().getFullYear();
        return currentYear - this.age;
    }

    // Setter
    set age(newAge) {
        if (newAge > 0 && newAge < 120) {
            this._age = newAge;
        }
    }

    // Static method
    static fromObject(obj) {
        return new Person(obj.name, obj.age);
    }
}

// Using the class
const person = new Person("Alice", 30);
console.log(person.greet()); // Hello, I'm Alice and I'm 30 years old.
console.log(person.birthYear); // 1994

const personFromObj = Person.fromObject({ name: "Bob", age: 25 });
console.log(personFromObj.greet()); // Hello, I'm Bob and I'm 25 years old.

Inheritance

class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        return `${this.name} makes a sound`;
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name); // Call parent constructor
        this.breed = breed;
    }

    // Override parent method
    speak() {
        return `${this.name} barks`;
    }

    // New method
    fetch() {
        return `${this.name} fetches the ball`;
    }
}

const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.speak()); // Buddy barks
console.log(dog.fetch()); // Buddy fetches the ball

Destructuring Advanced

Function Return Destructuring

function getUser() {
    return {
        name: "Alice",
        age: 30,
        city: "New York",
        hobbies: ["coding", "reading"]
    };
}

// Destructure function return
const { name, age, city } = getUser();
console.log(`${name} is ${age} years old and lives in ${city}`);

// Destructure with rename
const { name: userName, city: location } = getUser();
console.log(`${userName} lives in ${location}`);

// Destructure nested objects
const { name, hobbies: [firstHobby] } = getUser();
console.log(`${name}'s first hobby is ${firstHobby}`);

Array Destructuring Advanced

const users = [
    { id: 1, name: "Alice", active: true },
    { id: 2, name: "Bob", active: false },
    { id: 3, name: "Charlie", active: true }
];

// Find and destructure in one step
const activeUser = users.find(({ active }) => active);
const { name: activeUserName } = activeUser || {};
console.log(activeUserName); // Alice

// Skip elements with rest
const [first, , third, ...rest] = [1, 2, 3, 4, 5];
console.log(first, third); // 1 3
console.log(rest); // [4, 5]

Practical Examples

Modern Shopping Cart

class ShoppingCart {
    constructor() {
        this.items = [];
        this.total = 0;
    }

    addItem(product, quantity = 1) {
        const existingItem = this.items.find(item => item.id === product.id);
        
        if (existingItem) {
            existingItem.quantity += quantity;
        } else {
            this.items.push({ ...product, quantity });
        }
        
        this.calculateTotal();
    }

    removeItem(productId) {
        this.items = this.items.filter(item => item.id !== productId);
        this.calculateTotal();
    }

    calculateTotal() {
        this.total = this.items.reduce((sum, item) => {
            return sum + (item.price * item.quantity);
        }, 0);
    }

    checkout() {
        return {
            items: [...this.items],
            total: this.total
        };
    }
}

// Usage
const cart = new ShoppingCart();
cart.addItem({ id: 1, name: "Laptop", price: 999 });
cart.addItem({ id: 2, name: "Mouse", price: 29 }, 2);
cart.removeItem(1);

const order = cart.checkout();
console.log(order);

Modern API Client

class ApiClient {
    constructor(baseURL) {
        this.baseURL = baseURL;
        this.cache = new Map();
    }

    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        const cacheKey = `${endpoint}${JSON.stringify(options)}`;
        
        // Check cache first
        if (this.cache.has(cacheKey)) {
            return this.cache.get(cacheKey);
        }
        
        try {
            const response = await fetch(url, {
                headers: {
                    'Content-Type': 'application/json',
                    ...options.headers
                },
                ...options
            });
            
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            
            const data = await response.json();
            
            // Cache the result
            this.cache.set(cacheKey, data);
            
            return data;
        } catch (error) {
            console.error('API request failed:', error);
            throw error;
        }
    }

    // Convenient methods
    async get(endpoint) {
        return this.request(endpoint);
    }

    async post(endpoint, data) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }
}

// Usage
const api = new ApiClient('https://api.example.com');

const user = await api.get('/users/1');
const newUser = await api.post('/users', { 
    name: 'John', 
    email: 'john@example.com' 
});

Browser Compatibility

Checking Support

// Feature detection
const supportsArrowFunctions = (() => {}).toString().includes('=>');
const supportsAsyncAwait = async () => {}.constructor.name === 'AsyncFunction';
const supportsClasses = class {}.constructor === 'function';

console.log('Arrow functions:', supportsArrowFunctions);
console.log('Async/await:', supportsAsyncAwait);
console.log('Classes:', supportsClasses);

Polyfill Example

// Array.includes polyfill for older browsers
if (!Array.prototype.includes) {
    Array.prototype.includes = function(searchElement, fromIndex) {
        const array = Object(this);
        const len = array.length >>> 0;
        const from = fromIndex | 0;
        
        for (let i = from; i < len; i++) {
            if (array[i] === searchElement) {
                return true;
            }
        }
        return false;
    };
}

Best Practices

Use Modern Features Judiciously

// Use const by default
const API_KEY = 'your-api-key';
const MAX_ATTEMPTS = 3;

// Use let only when you need to reassign
let counter = 0;
counter++;

// Use default parameters
function createUser({ name = 'Guest', role = 'user' } = {}) {
    return { name, role };
}

// Use destructuring for cleaner code
function processUser({ name, age, email }) {
    console.log(name, age, email);
}

// Use async/await instead of promise chains
async function fetchUserData() {
    try {
        const user = await fetchUser();
        const posts = await fetchUserPosts(user.id);
        return { user, posts };
    } catch (error) {
        console.error('Failed to fetch user data:', error);
    }
}

// Use template literals for string building
const message = `Hello ${name}, you have ${unreadCount} new messages.`;

External Resources:

Related Tutorials:

Last updated on