The Module Pattern

The Module pattern provides a way to encapsulate and organize code, creating private and public members within an object. This pattern is particularly useful for grouping related functions and variables, reducing global scope pollution:


const CalculatorModule = (() => {
    let result = 0;

    const add = (num) => result += num;
    const subtract = (num) => result -= num;
    const getResult = () => result;

    return { add, subtract, getResult };
})();

// Usage
CalculatorModule.add(5);
CalculatorModule.subtract(2);
console.log(CalculatorModule.getResult()); // Output: 3

In this example, `CalculatorModule` encapsulates `result` and the methods `add`, `subtract`, and `getResult`. The `result` variable is private, while the returned object exposes only the functions, keeping the internal state secure.

The Prototype Pattern

The Prototype pattern in JavaScript allows for the creation of objects that share properties and methods, saving memory by avoiding duplication. It’s especially useful for objects that share common functionality, as it enables inheritance without copying:


function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function() {
    return `Hello, my name is ${this.name}`;
};

// Usage
const alice = new Person("Alice");
console.log(alice.sayHello()); // Output: Hello, my name is Alice

In this example, `sayHello` is defined on the `Person` prototype, allowing all instances of `Person` to access it without duplicating the function. This approach is memory-efficient, especially when creating multiple objects with shared behavior.

Using Object.create for Prototypal Inheritance


const animal = {
    type: "Animal",
    speak() {
        return `I am an ${this.type}`;
    },
};

const dog = Object.create(animal);
dog.type = "Dog";

console.log(dog.speak()); // Output: I am a Dog

Here, `Object.create` creates `dog` as an object that inherits from `animal`, demonstrating prototypal inheritance in a clean, straightforward way.

The Singleton Pattern

The Singleton pattern ensures that a class has only one instance, providing a global point of access to that instance. This pattern is useful for managing a shared resource, such as a configuration object or logging service:


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

    log(message) {
        console.log(`Log: ${message}`);
    }
}

// Usage
const logger1 = new Logger();
const logger2 = new Logger();
logger1.log("This is a singleton pattern."); // Output: Log: This is a singleton pattern.

console.log(logger1 === logger2); // Output: true

In this example, `Logger` ensures that only one instance is created, even if the constructor is called multiple times. By returning `Logger.instance`, we enforce a single, shared instance.

Comparing Module, Prototype, and Singleton Patterns

Each pattern serves different purposes and is suited for specific scenarios:

  • Module Pattern: Useful for encapsulating code and creating namespaces, especially in large applications.
  • Prototype Pattern: Ideal for sharing methods across instances without duplicating them, enabling efficient memory use.
  • Singleton Pattern: Ensures a single instance of a class, useful for shared resources or global state management.

Combining Patterns for Flexible Architectures

JavaScript’s flexibility allows for combining patterns to build complex architectures. For example, a singleton instance could use the Module pattern to organize its methods, or prototype-based inheritance could be used within a module:


const SettingsModule = (() => {
    let instance;

    const createInstance = () => {
        return {
            config: {},
            set(key, value) {
                this.config[key] = value;
            },
            get(key) {
                return this.config[key];
            }
        };
    };

    return {
        getInstance() {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();

// Usage
const settings = SettingsModule.getInstance();
settings.set("theme", "dark");
console.log(settings.get("theme")); // Output: dark

In this example, `SettingsModule` combines Singleton and Module patterns. It ensures a single instance, while encapsulating configuration management methods within the module.

Best Practices for Using Advanced JavaScript Patterns

  • Use Modules for Encapsulation: The Module pattern is effective for organizing code and avoiding global variables.
  • Leverage Prototypes for Efficiency: The Prototype pattern optimizes memory use by sharing methods across instances.
  • Apply Singletons for Shared State: Singleton is useful for managing shared resources that need a single point of access.

Conclusion

Advanced JavaScript patterns, including Module, Prototype, and Singleton, provide powerful solutions for organizing code, managing resources, and optimizing memory usage. These patterns are essential tools for structuring larger applications, enabling scalability, and ensuring maintainability. Understanding when and how to apply these patterns will help you build robust, efficient applications that handle complex requirements with ease.