Skip to main content

Module Patterns and Encapsulation in JavaScript

Created: May 8, 2026 Larry Qu 7 min read

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() { }
    ```javascript
    
  2. Encapsulate private data:
    // ✅ Good
    class User {
      #password;
      constructor(password) {
        this.#password = password;
      }
    }
    
    // ❌ Bad
    class User {
      constructor(password) {
        this.password = password;
      }
    }
    ```javascript
    
  3. Use clear module boundaries:
    // ✅ Good
    // Each file is a module with clear responsibility
    
    // ❌ Bad
    // Everything in one file
    ```javascript
    

Common Mistakes

  1. Global namespace pollution:
    // ❌ Bad
    window.myVar = 'value';
    window.myFunction = function() { };
    
    // ✅ Good
    const MyModule = (function() {
      return { myFunction() { } };
    })();
    ```javascript
    
  2. Circular dependencies:
    // ❌ Bad
    // module-a.js imports module-b
    // module-b.js imports module-a
    
    // ✅ Good
    // Restructure to avoid circular dependencies
    ```javascript
    
  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

Resources

Comments

Share this article

Scan to read on mobile