ES6+ Features and Modern JavaScript

Difficulty

Arrow functions are a concise syntax for writing function expressions in ES6. Here are the key differences:

Syntax Differences:

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

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

// Multiple parameters
const multiply = (a, b) => {
  return a * b;
};

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

// No parameters
const getTime = () => new Date();

Key Differences:

  1. this Binding:
const obj = {
  name: 'John',
  regularFunction: function() {
    console.log(this.name); // 'John' - this refers to obj
  },
  arrowFunction: () => {
    console.log(this.name); // undefined - this refers to global/window
  }
};

obj.regularFunction(); // 'John'
obj.arrowFunction(); // undefined
  1. Arguments Object:
function regularFunction() {
  console.log(arguments); // Has arguments object
}

const arrowFunction = () => {
  console.log(arguments); // ReferenceError: arguments is not defined
};
  1. Constructor Usage:
function RegularConstructor() {
  this.name = 'test';
}

const ArrowConstructor = () => {
  this.name = 'test';
};

new RegularConstructor(); // Works
new ArrowConstructor(); // TypeError: ArrowConstructor is not a constructor
  1. Hoisting:
// Regular functions are hoisted
console.log(regularFunc()); // Works - 'Hello'

function regularFunc() {
  return 'Hello';
}

// Arrow functions are not hoisted
console.log(arrowFunc()); // ReferenceError: Cannot access 'arrowFunc' before initialization

const arrowFunc = () => 'Hello';

When to Use Each:

Use Arrow Functions When:

  • Writing short, simple functions
  • Working with array methods (map, filter, reduce)
  • Need lexical this binding
  • Writing functional programming style code
// Great for array methods
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);

Use Regular Functions When:

  • Need this to be dynamically bound
  • Need access to arguments object
  • Creating constructors
  • Need function hoisting
  • Writing methods in objects/classes
// Object methods should use regular functions
const calculator = {
  value: 0,
  add: function(num) {
    this.value += num;
    return this;
  },
  multiply: function(num) {
    this.value *= num;
    return this;
  }
};

Destructuring assignment allows you to extract values from arrays or properties from objects into distinct variables.

Array Destructuring:

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

// Skip elements
const [primary, , tertiary] = colors;
console.log(primary); // 'red'
console.log(tertiary); // 'blue'

// Default values
const [a, b, c, d = 'yellow'] = colors;
console.log(d); // 'yellow'

// Rest operator
const [head, ...tail] = colors;
console.log(head); // 'red'
console.log(tail); // ['green', 'blue']

// Swap variables
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2, 1

Object Destructuring:

const user = {
  id: 1,
  name: 'John Doe',
  email: 'john@example.com',
  address: {
    city: 'New York',
    country: 'USA'
  }
};

// Basic object destructuring
const { name, email, id } = user;
console.log(name); // 'John Doe'

// Rename variables
const { name: userName, email: userEmail } = user;
console.log(userName); // 'John Doe'

// Default values
const { name, age = 25 } = user;
console.log(age); // 25

// Nested destructuring
const { address: { city, country } } = user;
console.log(city); // 'New York'

// Rest operator
const { id, ...userInfo } = user;
console.log(userInfo); // { name: 'John Doe', email: 'john@example.com', address: {...} }

Practical Use Cases:

  1. Function Parameters:
// Instead of passing many parameters
function createUser(name, email, age, city, country) {
  // ...
}

// Use object destructuring
function createUser({ name, email, age = 18, address: { city, country } }) {
  return { name, email, age, city, country };
}

createUser({
  name: 'John',
  email: 'john@example.com',
  address: { city: 'NYC', country: 'USA' }
});
  1. API Response Handling:
async function fetchUser(id) {
  const response = await fetch(`/api/users/${id}`);
  const { data: user, status, message } = await response.json();
  
  if (status === 'success') {
    const { name, email, profile: { avatar, bio } } = user;
    return { name, email, avatar, bio };
  }
  throw new Error(message);
}
  1. React Props:
function UserCard({ user: { name, email, avatar }, onEdit, onDelete }) {
  return (
    <div>
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{email}</p>
      <button onClick={onEdit}>Edit</button>
      <button onClick={onDelete}>Delete</button>
    </div>
  );
}
  1. Array Methods:
const users = [
  { id: 1, name: 'John', role: 'admin' },
  { id: 2, name: 'Jane', role: 'user' }
];

// Destructure in map
const userNames = users.map(({ name }) => name);

// Destructure in filter
const admins = users.filter(({ role }) => role === 'admin');

The spread (...) and rest (...) operators use the same syntax but serve different purposes depending on context.

Spread Operator (...): Expands an iterable (array, string, object) into individual elements.

Array Spread:

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

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

// Add elements
const withExtra = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]

// Clone array
const cloned = [...arr1]; // [1, 2, 3] - shallow copy

// Convert string to array
const chars = [...'hello']; // ['h', 'e', 'l', 'l', 'o']

// Function arguments
function sum(a, b, c) {
  return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

Object Spread:

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };

// Combine objects
const combined = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }

// Override properties
const updated = { ...obj1, b: 10 }; // { a: 1, b: 10 }

// Clone object
const cloned = { ...obj1 }; // { a: 1, b: 2 } - shallow copy

// Add properties
const withExtra = { ...obj1, e: 5 }; // { a: 1, b: 2, e: 5 }

Rest Operator (...): Collects multiple elements into a single variable.

Function Parameters:

// Collect remaining arguments
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

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

// Mix with regular parameters
function greet(greeting, ...names) {
  return `${greeting} ${names.join(', ')}!`;
}

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

Array Destructuring:

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

// Skip elements
const [a, , ...remaining] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(remaining); // [3, 4, 5]

Object Destructuring:

const { name, ...otherProps } = { name: 'John', age: 30, city: 'NYC' };
console.log(name); // 'John'
console.log(otherProps); // { age: 30, city: 'NYC' }

Practical Use Cases:

  1. Array Manipulation:
// Remove duplicates
const unique = [...new Set([1, 2, 2, 3, 3, 4])]; // [1, 2, 3, 4]

// Insert at specific position
function insertAt(array, index, ...items) {
  return [...array.slice(0, index), ...items, ...array.slice(index)];
}

const result = insertAt([1, 2, 5], 2, 3, 4); // [1, 2, 3, 4, 5]
  1. Object Updates:
// Immutable updates
const updateUser = (user, updates) => ({ ...user, ...updates });

const user = { name: 'John', age: 30 };
const updated = updateUser(user, { age: 31, city: 'NYC' });
// { name: 'John', age: 31, city: 'NYC' }
  1. React State Updates:
// Add item to array
const addItem = (items, newItem) => [...items, newItem];

// Remove item from array
const removeItem = (items, id) => items.filter(item => item.id !== id);

// Update object in array
const updateItem = (items, id, updates) => 
  items.map(item => item.id === id ? { ...item, ...updates } : item);
  1. API Calls:
// Merge query parameters
const buildUrl = (baseUrl, ...params) => {
  const queryString = params.join('&');
  return `${baseUrl}?${queryString}`;
};

const url = buildUrl('/api/users', 'page=1', 'limit=10', 'sort=name');

Template literals are string literals that allow embedded expressions and multi-line strings.

Basic Template Literals:

const name = 'John';
const age = 30;

// String interpolation
const message = `Hello, my name is ${name} and I am ${age} years old.`;

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

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

Tagged Template Literals:

function myTag(strings, ...values) {
  return strings.reduce((result, string, i) => {
    return result + string + (values[i] || '');
  }, '');
}

const name = 'John';
const result = myTag`Hello ${name}, you are ${30} years old.`;

// Practical example: SQL query builder
function sql(strings, ...values) {
  return strings.reduce((query, string, i) => {
    const value = values[i];
    const escaped = typeof value === 'string' ? `'${value.replace(/'/g, "''")}'` : value;
    return query + string + (escaped || '');
  }, '');
}

const userId = 123;
const query = sql`SELECT * FROM users WHERE id = ${userId}`;

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

Export Syntax:

// Named exports
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator { multiply(a, b) { return a * b; } }

// Default export
export default function subtract(a, b) { return a - b; }

// Mixed exports
export const VERSION = '1.0.0';
export default class MathUtils { static divide(a, b) { return a / b; } }

Import Syntax:

// Named imports
import { PI, add, Calculator } from './math.js';

// Default import
import subtract from './math.js';

// Mixed imports
import MathUtils, { VERSION } from './math.js';

// Namespace import
import * as math from './math.js';

// Dynamic import
const { add, subtract } = await import('./math.js');

CommonJS vs ES6 Modules:

// CommonJS
const fs = require('fs');
module.exports = { readFile: fs.readFile };

// ES6 Modules
import fs from 'fs';
export { readFile: fs.readFile };

Module Bundling:

// Code splitting
const Home = lazy(() => import('./components/Home'));
const About = lazy(() => import('./components/About'));

// Tree shaking (only used exports are bundled)
import { debounce } from 'lodash-es'; // Only debounce is bundled
import _ from 'lodash'; // Entire library bundled