模块模式

JavaScript 模块模式的实现与应用

问题

什么是模块模式?如何用 JavaScript 实现模块模式?

解答

模块模式利用闭包创建私有作用域,实现变量和方法的封装。

基本模块模式

const Counter = (function() {
  // 私有变量
  let count = 0;
  
  // 私有方法
  function log(msg) {
    console.log(msg);
  }
  
  // 返回公共接口
  return {
    increment() {
      count++;
      log(`Count: ${count}`);
    },
    decrement() {
      count--;
      log(`Count: ${count}`);
    },
    getCount() {
      return count;
    }
  };
})();

Counter.increment(); // Count: 1
Counter.increment(); // Count: 2
console.log(Counter.getCount()); // 2
console.log(Counter.count); // undefined(无法访问私有变量)

揭示模块模式

const Calculator = (function() {
  let result = 0;
  
  function add(x) {
    result += x;
    return this;
  }
  
  function subtract(x) {
    result -= x;
    return this;
  }
  
  function multiply(x) {
    result *= x;
    return this;
  }
  
  function getResult() {
    return result;
  }
  
  function reset() {
    result = 0;
    return this;
  }
  
  // 揭示公共方法,映射到私有函数
  return {
    add,
    subtract,
    multiply,
    getResult,
    reset
  };
})();

Calculator.add(10).subtract(3).multiply(2);
console.log(Calculator.getResult()); // 14

带参数的模块

const UserModule = (function(initialName) {
  let name = initialName;
  const history = [];
  
  function recordChange(oldName, newName) {
    history.push({ oldName, newName, time: Date.now() });
  }
  
  return {
    getName() {
      return name;
    },
    setName(newName) {
      recordChange(name, newName);
      name = newName;
    },
    getHistory() {
      return [...history]; // 返回副本,防止外部修改
    }
  };
})('Guest');

console.log(UserModule.getName()); // Guest
UserModule.setName('Alice');
console.log(UserModule.getName()); // Alice
console.log(UserModule.getHistory()); // [{ oldName: 'Guest', newName: 'Alice', time: ... }]

模块扩展

const BaseModule = (function() {
  let data = [];
  
  return {
    add(item) {
      data.push(item);
    },
    getAll() {
      return [...data];
    }
  };
})();

// 扩展模块
const ExtendedModule = (function(base) {
  return {
    ...base,
    remove(item) {
      const all = base.getAll();
      const index = all.indexOf(item);
      if (index > -1) {
        // 注意:这里无法直接操作原模块的私有 data
        // 需要原模块提供 remove 方法
      }
    },
    count() {
      return base.getAll().length;
    }
  };
})(BaseModule);

ExtendedModule.add('a');
ExtendedModule.add('b');
console.log(ExtendedModule.count()); // 2

关键点

  • IIFE(立即执行函数)创建独立作用域,防止污染全局
  • 闭包保持对私有变量的引用,实现真正的私有性
  • 返回对象作为公共 API,控制暴露的接口
  • 揭示模块模式让公共方法命名更清晰,便于维护
  • ES6 模块(import/export)是现代替代方案,但模块模式在理解闭包和封装上仍有价值