← Back to Articles

JavaScript: Advanced

Closures

// Closure: function + lexical scope
function outer() {
    let count = 0;
    return function inner() {
        count++;
        return count;
    };
}
const counter = outer();
counter(); // 1
counter(); // 2

// Practical: Private variables
function createCounter() {
    let private = 0;
    return {
        increment: () => private++,
        get: () => private
    };
}

// Practical: Function factories
function multiplyBy(factor) {
    return (num) => num * factor;
}
const double = multiplyBy(2);
double(5); // 10

Scope Chain

// Global scope
const global = 'global';

function outer() {
    // Function scope
    const outerVar = 'outer';
    
    function inner() {
        // Inner scope
        const innerVar = 'inner';
        console.log(global); // ✓
        console.log(outerVar); // ✓
        console.log(innerVar); // ✓
    }
}

// Block scope
{
    let blockVar = 'block';
    const blockConst = 'const';
}
// console.log(blockVar); // ✗ ReferenceError

Prototypes

// Prototype chain
const obj = { a: 1 };
console.log(obj.toString()); // From Object.prototype

// Constructor function
function Person(name) {
    this.name = name;
}
Person.prototype.greet = function() {
    return `Hi, I'm ${this.name}`;
};
const john = new Person('John');
john.greet();

// Prototypal inheritance
function Employee(name, role) {
    Person.call(this, name);
    this.role = role;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;

Classes

// Class definition
class Person {
    constructor(name) {
        this.name = name;
    }
    
    greet() {
        return `Hi, I'm ${this.name}`;
    }
    
    // Static method
    static create(name) {
        return new Person(name);
    }
}

// Inheritance
class Employee extends Person {
    constructor(name, role) {
        super(name);
        this.role = role;
    }
    
    greet() {
        return `${super.greet()}, I'm a ${this.role}`;
    }
}

// Getters and setters
class Circle {
    constructor(radius) {
        this._radius = radius;
    }
    
    get area() {
        return Math.PI * this._radius ** 2;
    }
    
    set radius(value) {
        if (value <= 0) throw new Error('Invalid');
        this._radius = value;
    }
}

Modules

// Export (module.js)
export const value = 42;
export function fn() {}
export class MyClass {}
export default class DefaultClass {}

// Named exports
export { value, fn, MyClass };

// Import
import DefaultClass from './module.js';
import { value, fn } from './module.js';
import * as Module from './module.js';
import Default, { named } from './module.js';

// Re-export
export { value } from './module.js';
export * from './module.js';

Design Patterns

// Module Pattern
const Module = (function() {
    let private = 0;
    return {
        public: () => private++
    };
})();

// Singleton
class Singleton {
    constructor() {
        if (Singleton.instance) return Singleton.instance;
        Singleton.instance = this;
    }
}

// Observer Pattern
class EventEmitter {
    constructor() { this.events = {}; }
    on(event, fn) {
        (this.events[event] = this.events[event] || []).push(fn);
    }
    emit(event, ...args) {
        (this.events[event] || []).forEach(fn => fn(...args));
    }
}

// Factory Pattern
function createAnimal(type) {
    const animals = {
        dog: { sound: 'woof' },
        cat: { sound: 'meow' }
    };
    return { ...animals[type], type };
}

Key Takeaways

  • Closures enable private state and function factories
  • Prototypes power JavaScript inheritance
  • Classes provide cleaner syntax over prototypes
  • Modules organize code into reusable units
  • Design patterns solve common problems

Next Steps