ES6+ Features and Modern JavaScript

Modern JavaScript: arrow functions, destructuring, spread/rest, modules, async/await, and key browser APIs.

Explain arrow functions and their differences from regular functions. When should you use each?

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;
  }
};

Explain destructuring assignment. Provide examples of object and array destructuring with practical use cases.

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');

Explain the spread and rest operators. When would you use each?

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');

Explain template literals and tagged template literals. Provide practical examples.

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}`;

Explain ES6 modules (import/export). Compare with CommonJS and discuss module bundling.

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