← Back to Module

JavaScript Advanced Concepts

CMU422: Fundamentals of Web Design - Session 8

Birmingham Newman University

Lecturer: James Williams

Functions, objects, and modern JavaScript

3-hour session • 26 slides • 2 interactive tasks

Session Timeline:

  • 10 min: Registration & waiting
  • 20 min: Opening slides
  • 45 min: Task 1
  • 15 min: Break/Catch up
  • 20 min: Secondary slides
  • 45 min: Task 2
  • Remaining: Self-study

Learning Objectives

  • Master advanced function concepts
  • Understand object-oriented programming
  • Learn modern JavaScript features
  • Work with asynchronous programming
  • Apply functional programming concepts

Advanced Function Concepts

Functions are first-class citizens in JavaScript
// Function declaration
function greet(name) { return `Hello ${name}`; }

// Function expression
const greet = function(name) { return `Hello ${name}`; };

// Arrow function
const greet = (name) => `Hello ${name}`;

// Immediately Invoked Function Expression (IIFE)
(function() { console.log('IIFE'); })();
  • Multiple ways to define functions
  • Functions can be assigned to variables
  • Functions can be passed as arguments
  • Functions can return other functions

Higher-Order Functions

// Function that returns a function
function multiply(x) {
  return function(y) {
    return x * y;
  };
}

const multiplyByTwo = multiply(2);
console.log(multiplyByTwo(5)); // 10

// Function that takes a function as argument
function processArray(arr, callback) {
  return arr.map(callback);
}

const numbers = [1, 2, 3, 4];
const doubled = processArray(numbers, x => x * 2);
  • Functions that return functions
  • Functions that take functions as arguments
  • Enable functional programming patterns
  • Create reusable and composable code

Try It: Advanced JavaScript Concepts

Closure Demo

Click buttons to see closures in action!

Current Concept:

Closures allow functions to access variables from their outer scope even after the outer function has returned.

Explore advanced JavaScript concepts with interactive examples!

Closures

function createCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// Private variables
function createWallet(initialBalance) {
  let balance = initialBalance;
  return {
    getBalance: () => balance,
    deposit: (amount) => { balance += amount; },
    withdraw: (amount) => {
      if (amount <= balance) {
        balance -= amount;
        return true;
      }
      return false;
    }
  };
}
  • Functions that remember their lexical scope
  • Access to variables from outer scope
  • Used for data privacy and state management
  • Common in modern JavaScript patterns

Object-Oriented Programming

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

Person.prototype.greet = function() {
  return `Hello, I'm ${this.name}`;
};

const person = new Person('John', 30);
console.log(person.greet()); // "Hello, I'm John"

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

  greet() {
    return `Hello, I'm ${this.name}`;
  }
}
  • Constructor functions and prototypes
  • ES6 class syntax (syntactic sugar)
  • Inheritance and polymorphism
  • Encapsulation and abstraction

Inheritance

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

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

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

  speak() {
    return `${this.name} barks`;
  }

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

const dog = new Dog('Buddy', 'Golden Retriever');
console.log(dog.speak()); // "Buddy barks"
  • Use extends for inheritance
  • Call super() in constructor
  • Override methods in child classes
  • Add new methods to child classes

Modern JavaScript Features

// Destructuring
const { name, age } = person;
const [first, second, ...rest] = array;

// Spread operator
const newArray = [...oldArray, newItem];
const newObject = { ...oldObject, newProperty: value };

// Template literals
const message = `Hello ${name}, you are ${age} years old`;

// Default parameters
function greet(name = 'Guest') {
  return `Hello ${name}`;
}

// Rest parameters
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}
  • Destructuring for cleaner code
  • Spread operator for copying and merging
  • Template literals for string interpolation
  • Default and rest parameters

Promises

// Creating a promise
const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const random = Math.random();
    if (random > 0.5) {
      resolve('Success!');
    } else {
      reject('Failed!');
    }
  }, 1000);
});

// Using promises
myPromise
  .then(result => console.log(result))
  .catch(error => console.error(error));

// Promise chaining
fetch('/api/data')
  .then(response => response.json())
  .then(data => processData(data))
  .catch(error => handleError(error));
  • Handle asynchronous operations
  • Better than callbacks
  • Chain multiple operations
  • Error handling with catch

Async/Await

// Async function
async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      throw new Error('User not found');
    }
    const userData = await response.json();
    return userData;
  } catch (error) {
    console.error('Error fetching user:', error);
    throw error;
  }
}

// Using async function
async function displayUser(userId) {
  const user = await fetchUserData(userId);
  console.log(`User: ${user.name}`);
}

// Arrow function with async
const loadData = async () => {
  const data = await fetch('/api/data');
  return data.json();
};
  • Syntactic sugar over promises
  • Makes async code look synchronous
  • Better error handling with try/catch
  • Easier to read and understand

ES6 Modules

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

export default class Calculator {
  multiply(a, b) { return a * b; }
}

// app.js (importing)
import Calculator, { add, subtract } from './math.js';

const calc = new Calculator();
console.log(add(5, 3)); // 8
console.log(calc.multiply(4, 2)); // 8

// Dynamic imports
const module = await import('./dynamic-module.js');
  • Organize code into separate files
  • Export and import functionality
  • Default and named exports
  • Dynamic imports for code splitting

Functional Programming Concepts

// Pure functions
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

// Function composition
const compose = (...fns) => x =>
  fns.reduceRight((acc, fn) => fn(acc), x);

const addOne = x => x + 1;
const double = x => x * 2;
const addOneAndDouble = compose(double, addOne);

console.log(addOneAndDouble(5)); // 12

// Immutability
const original = [1, 2, 3];
const doubled = original.map(x => x * 2);
// original is unchanged
  • Pure functions (no side effects)
  • Function composition
  • Immutability
  • Higher-order functions

Modern Array Methods

const numbers = [1, 2, 3, 4, 5];

// Map
const doubled = numbers.map(x => x * 2);
// [2, 4, 6, 8, 10]

// Filter
const evens = numbers.filter(x => x % 2 === 0);
// [2, 4]

// Reduce
const sum = numbers.reduce((acc, x) => acc + x, 0);
// 15

// Find
const firstEven = numbers.find(x => x % 2 === 0);
// 2

// Some
const hasEven = numbers.some(x => x % 2 === 0);
// true

// Every
const allPositive = numbers.every(x => x > 0);
// true
  • Functional programming approach
  • Chain multiple operations
  • More readable than loops
  • Immutable operations

Advanced Error Handling

// Custom error classes
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

// Error handling with async/await
async function processData(data) {
  try {
    if (!data) {
      throw new ValidationError('Data is required', 'data');
    }

    const result = await validateAndProcess(data);
    return result;
  } catch (error) {
    if (error instanceof ValidationError) {
      console.error(`Validation error in ${error.field}:`, error.message);
    } else {
      console.error('Unexpected error:', error);
    }
    throw error;
  }
}
  • Custom error classes
  • Error inheritance
  • Type checking with instanceof
  • Proper error propagation

Performance Optimization

  • Memoization for expensive calculations
  • Debouncing and throttling
  • Lazy loading and code splitting
  • Efficient DOM manipulation
  • Memory leak prevention
// Memoization
function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// Debouncing
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

Testing JavaScript Code

  • Unit testing with Jest or Mocha
  • Test-driven development (TDD)
  • Mocking and stubbing
  • Integration testing
  • Code coverage
// Example test with Jest
function add(a, b) {
  return a + b;
}

// test.js
describe('add function', () => {
  test('adds two positive numbers', () => {
    expect(add(2, 3)).toBe(5);
  });

  test('adds negative numbers', () => {
    expect(add(-1, -2)).toBe(-3);
  });

  test('handles zero', () => {
    expect(add(0, 5)).toBe(5);
  });
});

Advanced Debugging

  • Browser developer tools
  • Console methods (log, warn, error, table)
  • Breakpoints and step-through
  • Performance profiling
  • Memory leak detection
// Debugging techniques
console.log('Debug info:', { user, data });
console.table(arrayOfObjects);
console.trace('Function call stack');

// Performance measurement
console.time('operation');
// ... expensive operation
console.timeEnd('operation');

// Debugger statement
function complexFunction() {
  let result = 0;
  debugger; // Execution stops here
  return result;
}

Task 1: Advanced JavaScript Library

Instructions:

  1. Create a new JavaScript file called advanced-library.js
  2. Build a utility library with:
    • Math utilities: Advanced mathematical functions
    • Array utilities: Custom array manipulation methods
    • Object utilities: Deep cloning, merging, validation
    • Async utilities: Promise helpers and async operations
  3. Use modern JavaScript features:
    • ES6 classes and modules
    • Arrow functions and destructuring
    • Promises and async/await
    • Higher-order functions
  4. Implement proper error handling
  5. Add JSDoc documentation
  6. Create unit tests for your functions

Time: 45 minutes

This task will help you understand advanced JavaScript concepts and patterns

Break Time

15 Minutes

Take a break, ask questions, or catch up on the previous task.

Next: Secondary slides and Task 2

Common Design Patterns

  • Singleton: Single instance pattern
  • Factory: Object creation pattern
  • Observer: Event handling pattern
  • Module: Encapsulation pattern
  • Decorator: Function enhancement pattern
// Singleton pattern
class Database {
  constructor() {
    if (Database.instance) {
      return Database.instance;
    }
    Database.instance = this;
  }
}

// Factory pattern
class UserFactory {
  createUser(type, data) {
    switch(type) {
      case 'admin': return new AdminUser(data);
      case 'regular': return new RegularUser(data);
    }
  }
}

Memory Management

  • Garbage collection
  • Memory leaks prevention
  • Event listener cleanup
  • Closure memory considerations
  • WeakMap and WeakSet usage
// Memory leak prevention
class EventManager {
  constructor() {
    this.listeners = new Map();
  }

  addListener(element, event, handler) {
    element.addEventListener(event, handler);
    this.listeners.set(element, { event, handler });
  }

  removeListener(element) {
    const listener = this.listeners.get(element);
    if (listener) {
      element.removeEventListener(listener.event, listener.handler);
      this.listeners.delete(element);
    }
  }

  cleanup() {
    this.listeners.forEach((listener, element) => {
      this.removeListener(element);
    });
  }
}

Security Best Practices

  • Input validation and sanitization
  • XSS prevention
  • CSRF protection
  • Secure coding practices
  • Content Security Policy (CSP)
// Input validation
function validateInput(input) {
  if (typeof input !== 'string') {
    throw new Error('Input must be a string');
  }
  return input.replace(/[<>]/g, ''); // Basic XSS prevention
}

// Secure object property access
const safeGet = (obj, path) => {
  return path.split('.').reduce((current, key) => {
    return current && current[key] !== undefined ? current[key] : null;
  }, obj);
};

Code Quality and Standards

  • ESLint for code linting
  • Prettier for code formatting
  • JSDoc for documentation
  • TypeScript for type safety
  • Code review practices
// JSDoc documentation
/**
* Calculates the factorial of a number
* @param {number} n - The number to calculate factorial for
* @returns {number} The factorial of n
* @throws {Error} If n is negative
*/
function factorial(n) {
  if (n < 0) {
    throw new Error('Factorial is not defined for negative numbers');
  }
  if (n === 0 || n === 1) {
    return 1;
  }
  return n * factorial(n - 1);
}

Task 2: Complete Web Application

Instructions:

  1. Create a complete web application using all concepts learned
  2. Build a task management app with:
    • Architecture: Modular design with ES6 modules
    • Data Management: Local storage with CRUD operations
    • UI Components: Reusable components using classes
    • Async Operations: Simulated API calls with promises
    • Event Handling: Advanced event delegation and custom events
  3. Implement advanced features:
    • Task filtering and sorting
    • Drag and drop functionality
    • Real-time search with debouncing
    • Data validation and error handling
    • Responsive design with modern CSS
  4. Use design patterns (Factory, Observer, Module)
  5. Add comprehensive error handling and logging
  6. Include unit tests for core functionality

Time: 45 minutes

This task will help you understand full-stack JavaScript development

Common Advanced Issues

  • Scope issues: Check closure and context
  • Async problems: Promise rejection handling
  • Memory leaks: Event listener cleanup
  • Performance issues: Profiling and optimization
  • Module errors: Import/export syntax
  • Type errors: Runtime type checking
// Common debugging patterns
// Check if function is called
console.log('Function called with:', arguments);

// Check promise state
promise.then(result => {
  console.log('Promise resolved:', result);
}).catch(error => {
  console.error('Promise rejected:', error);
});

// Check object structure
console.log('Object keys:', Object.keys(obj));
console.log('Object values:', Object.values(obj));

Session Summary

  • Master advanced function concepts and closures
  • Understand object-oriented programming
  • Use modern JavaScript features effectively
  • Handle asynchronous operations properly
  • Apply functional programming concepts
  • Write maintainable and testable code

Next Session:

Web APIs and External Libraries - Working with third-party services