Skip to main content
โšก Calmops

Module Patterns and Encapsulation in JavaScript

Module Patterns and Encapsulation in JavaScript

Module patterns provide code organization and encapsulation. This article covers IIFE, CommonJS, ES6 modules, and advanced encapsulation techniques.

Introduction

Module patterns provide:

  • Code organization
  • Encapsulation
  • Namespace management
  • Dependency management
  • Code reusability

Understanding modules helps you:

  • Organize large codebases
  • Prevent global pollution
  • Manage dependencies
  • Improve maintainability
  • Enable code reuse

Immediately Invoked Function Expression (IIFE)

Basic IIFE

// โœ… Good: Basic IIFE for encapsulation
(function() {
  const privateVar = 'private';

  function privateFunction() {
    console.log(privateVar);
  }

  // Public API
  window.MyModule = {
    publicMethod() {
      privateFunction();
    }
  };
})();

// Usage
MyModule.publicMethod(); // private
console.log(typeof privateVar); // undefined (private)

IIFE with Parameters

// โœ… Good: IIFE with dependency injection
(function(window, document, undefined) {
  const privateData = {};

  function init() {
    console.log('Module initialized');
  }

  window.MyModule = {
    init,
    getData() {
      return privateData;
    }
  };
})(window, document);

// Usage
MyModule.init(); // Module initialized

Revealing Module Pattern

// โœ… Good: Revealing module pattern
const Calculator = (function() {
  // Private variables
  let result = 0;

  // Private functions
  function logOperation(operation, value) {
    console.log(`${operation}: ${value}`);
  }

  // Public API
  return {
    add(value) {
      result += value;
      logOperation('Add', value);
      return this;
    },

    subtract(value) {
      result -= value;
      logOperation('Subtract', value);
      return this;
    },

    multiply(value) {
      result *= value;
      logOperation('Multiply', value);
      return this;
    },

    getResult() {
      return result;
    },

    reset() {
      result = 0;
      console.log('Reset');
      return this;
    }
  };
})();

// Usage
Calculator.add(5).multiply(2).subtract(3);
console.log(Calculator.getResult()); // 7

CommonJS Modules

Basic CommonJS

// โœ… Good: CommonJS module (Node.js)
// math.js
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

module.exports = {
  add,
  subtract
};

// app.js
const math = require('./math');
console.log(math.add(5, 3)); // 8

CommonJS with Private Members

// โœ… Good: CommonJS with encapsulation
// logger.js
const fs = require('fs');

// Private
function formatMessage(level, message) {
  return `[${level}] ${new Date().toISOString()}: ${message}`;
}

// Public
module.exports = {
  info(message) {
    const formatted = formatMessage('INFO', message);
    console.log(formatted);
  },

  error(message) {
    const formatted = formatMessage('ERROR', message);
    console.error(formatted);
  }
};

// app.js
const logger = require('./logger');
logger.info('Application started');
logger.error('An error occurred');

ES6 Modules

Basic ES6 Modules

// โœ… Good: ES6 module
// math.js
export function add(a, b) {
  return a + b;
}

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

export const PI = 3.14159;

// app.js
import { add, subtract, PI } from './math.js';

console.log(add(5, 3)); // 8
console.log(PI); // 3.14159

Default Exports

// โœ… Good: Default export
// logger.js
class Logger {
  info(message) {
    console.log(`[INFO] ${message}`);
  }

  error(message) {
    console.error(`[ERROR] ${message}`);
  }
}

export default Logger;

// app.js
import Logger from './logger.js';

const logger = new Logger();
logger.info('Hello');

Named and Default Exports

// โœ… Good: Mixed exports
// utils.js
export function helper1() { }
export function helper2() { }

export default class Utils {
  static doSomething() { }
}

// app.js
import Utils, { helper1, helper2 } from './utils.js';

Utils.doSomething();
helper1();

Module Aliasing

// โœ… Good: Import aliasing
// app.js
import { add as addition, subtract as subtraction } from './math.js';
import * as math from './math.js';

console.log(addition(5, 3)); // 8
console.log(math.add(5, 3)); // 8

Advanced Encapsulation

Closure-based Encapsulation

// โœ… Good: Closure-based private members
class BankAccount {
  constructor(initialBalance) {
    let balance = initialBalance; // Private

    this.deposit = function(amount) {
      balance += amount;
      return balance;
    };

    this.withdraw = function(amount) {
      if (amount > balance) {
        throw new Error('Insufficient funds');
      }
      balance -= amount;
      return balance;
    };

    this.getBalance = function() {
      return balance;
    };
  }
}

// Usage
const account = new BankAccount(1000);
console.log(account.getBalance()); // 1000
console.log(account.deposit(500)); // 1500
console.log(account.withdraw(200)); // 1300
console.log(account.balance); // undefined (private)

WeakMap for Private Data

// โœ… Good: WeakMap for private data
const privateData = new WeakMap();

class User {
  constructor(name, email) {
    privateData.set(this, {
      name,
      email,
      password: 'secret'
    });
  }

  getName() {
    return privateData.get(this).name;
  }

  getEmail() {
    return privateData.get(this).email;
  }

  // Password is truly private
}

// Usage
const user = new User('John', '[email protected]');
console.log(user.getName()); // John
console.log(user.password); // undefined

Symbol for Private Properties

// โœ… Good: Symbol for private properties
const _password = Symbol('password');
const _email = Symbol('email');

class User {
  constructor(name, password, email) {
    this.name = name;
    this[_password] = password;
    this[_email] = email;
  }

  verifyPassword(password) {
    return this[_password] === password;
  }

  getEmail() {
    return this[_email];
  }
}

// Usage
const user = new User('John', 'secret123', '[email protected]');
console.log(user.name); // John
console.log(user[_password]); // secret123 (accessible but not obvious)
console.log(user.password); // undefined

Private Fields (ES2022)

// โœ… Good: Private fields (modern)
class User {
  #password; // Private field
  #email;

  constructor(name, password, email) {
    this.name = name;
    this.#password = password;
    this.#email = email;
  }

  verifyPassword(password) {
    return this.#password === password;
  }

  getEmail() {
    return this.#email;
  }
}

// Usage
const user = new User('John', 'secret123', '[email protected]');
console.log(user.name); // John
console.log(user.#password); // SyntaxError (truly private)

Practical Module Patterns

Plugin System

// โœ… Good: Plugin system
const PluginManager = (function() {
  const plugins = {};

  return {
    register(name, plugin) {
      if (plugins[name]) {
        throw new Error(`Plugin ${name} already registered`);
      }
      plugins[name] = plugin;
      console.log(`Plugin ${name} registered`);
    },

    unregister(name) {
      delete plugins[name];
      console.log(`Plugin ${name} unregistered`);
    },

    execute(name, ...args) {
      if (!plugins[name]) {
        throw new Error(`Plugin ${name} not found`);
      }
      return plugins[name](...args);
    },

    list() {
      return Object.keys(plugins);
    }
  };
})();

// Usage
PluginManager.register('uppercase', (str) => str.toUpperCase());
PluginManager.register('lowercase', (str) => str.toLowerCase());

console.log(PluginManager.execute('uppercase', 'hello')); // HELLO
console.log(PluginManager.list()); // ['uppercase', 'lowercase']

Configuration Module

// โœ… Good: Configuration module
const Config = (function() {
  const config = {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    debug: false
  };

  return {
    get(key) {
      return config[key];
    },

    set(key, value) {
      if (!(key in config)) {
        throw new Error(`Unknown config key: ${key}`);
      }
      config[key] = value;
    },

    getAll() {
      return { ...config };
    },

    reset() {
      Object.assign(config, {
        apiUrl: 'https://api.example.com',
        timeout: 5000,
        debug: false
      });
    }
  };
})();

// Usage
console.log(Config.get('apiUrl')); // https://api.example.com
Config.set('debug', true);
console.log(Config.getAll()); // { apiUrl: '...', timeout: 5000, debug: true }

Event Bus Module

// โœ… Good: Event bus module
const EventBus = (function() {
  const events = {};

  return {
    on(event, callback) {
      if (!events[event]) {
        events[event] = [];
      }
      events[event].push(callback);
    },

    off(event, callback) {
      if (events[event]) {
        events[event] = events[event].filter(cb => cb !== callback);
      }
    },

    emit(event, data) {
      if (events[event]) {
        events[event].forEach(callback => callback(data));
      }
    },

    once(event, callback) {
      const onceWrapper = (data) => {
        callback(data);
        this.off(event, onceWrapper);
      };
      this.on(event, onceWrapper);
    }
  };
})();

// Usage
EventBus.on('user:login', (user) => {
  console.log(`${user} logged in`);
});

EventBus.emit('user:login', 'John');
// John logged in

Best Practices

  1. Use ES6 modules when possible:

    // โœ… Good
    export function helper() { }
    import { helper } from './module.js';
    
    // โŒ Bad
    window.helper = function() { }
    
  2. Encapsulate private data:

    // โœ… Good
    class User {
      #password;
      constructor(password) {
        this.#password = password;
      }
    }
    
    // โŒ Bad
    class User {
      constructor(password) {
        this.password = password;
      }
    }
    
  3. Use clear module boundaries:

    // โœ… Good
    // Each file is a module with clear responsibility
    
    // โŒ Bad
    // Everything in one file
    

Common Mistakes

  1. Global namespace pollution:

    // โŒ Bad
    window.myVar = 'value';
    window.myFunction = function() { };
    
    // โœ… Good
    const MyModule = (function() {
      return { myFunction() { } };
    })();
    
  2. Circular dependencies:

    // โŒ Bad
    // module-a.js imports module-b
    // module-b.js imports module-a
    
    // โœ… Good
    // Restructure to avoid circular dependencies
    
  3. Exposing private data:

    // โŒ Bad
    class User {
      constructor(password) {
        this.password = password; // Exposed
      }
    }
    
    // โœ… Good
    class User {
      #password;
      constructor(password) {
        this.#password = password; // Private
      }
    }
    

Summary

Module patterns organize code effectively. Key takeaways:

  • IIFE: Encapsulation with functions
  • CommonJS: Node.js modules
  • ES6 Modules: Modern standard
  • Closures: Private data
  • WeakMap: Private object data
  • Symbols: Private properties
  • Private fields: True privacy
  • Improves organization
  • Enables encapsulation
  • Facilitates maintenance

Next Steps

Comments