DMarlon Studio

Lección 15 · 15 de 20

Patrón módulo e IIFE · JavaScript

Oculte el estado interno en JavaScript con IIFE y el patrón módulo, y vea cómo Node envuelve cada fichero y qué exporta module.exports.

JavaScript · Patrón módulo e IIFE en DMarlon Studio: el código del ejemplo y su salida en la terminal

Una IIFE (expresión de función invocada inmediatamente) es una función que se define y se ejecuta en el mismo momento: (function () { ... })(). Los paréntesis exteriores la convierten en una expresión y los últimos la llaman. Todo lo que se declara dentro, como items y log, queda en su ámbito privado.

El patrón módulo combina la IIFE con las clausuras: devuelve un objeto solo con lo que quiere hacer público (add y count), que siguen teniendo acceso a items. Desde fuera, inventory.items es undefined: el inventario solo se puede cambiar a través de su interfaz. Antes de que existieran los módulos, así se organizaba el JavaScript de las páginas web.

Node hace algo muy parecido con cada fichero CommonJS: lo envuelve en una función con cinco parámetros (exports, require, module, __filename y __dirname). Por eso arguments.length vale 5 en el nivel superior del fichero y sus variables no se mezclan con las de otros ficheros.

Lo que un fichero exporta es `module.exports`; exports es solo un atajo que al principio apunta al mismo objeto. Si asigna un objeto nuevo a module.exports, como hace el ejemplo, exports sigue apuntando al antiguo y deja de servir: use exports.name = ... para añadir o module.exports = ... para sustituir, nunca exports = .... Además, require() guarda cada módulo en caché, así que dos llamadas devuelven el mismo objeto.

Código · main.js

// IIFE: a function that runs at once and keeps its variables private.const inventory = (function () {  const items = new Map(); // nothing outside can reach this  const log = (msg) => console.log(`[inventory] ${msg}`);  return {    add(name, qty) {      items.set(name, (items.get(name) ?? 0) + qty);      log(`+${qty} ${name}`);    },    count: (name) => items.get(name) ?? 0,  };})();inventory.add("pens", 20);inventory.add("pens", 5);console.log("pens:", inventory.count("pens"), "| inventory.items:", inventory.items);// Node wraps every CommonJS file in a function just like that one:// (function (exports, require, module, __filename, __dirname) { ... })console.log("Wrapper arguments:", arguments.length, "|", typeof require, typeof module);console.log("exports === module.exports:", exports === module.exports);// Whatever ends up in module.exports is what require() returns.module.exports = { inventory, version: "1.2.0" };console.log("Exported:", Object.keys(module.exports).join(", "));console.log("exports still points to the old object:", exports !== module.exports);// require() caches modules: a second call returns the same object.const first = require("path");const second = require("path");console.log("Cached:", first === second, "| extname:", first.extname("orders.json"));

Salida · node main.js

[inventory] +20 pens
[inventory] +5 pens
pens: 25 | inventory.items: undefined
Wrapper arguments: 5 | function object
exports === module.exports: true
Exported: inventory, version
exports still points to the old object: true
Cached: true | extname: .json

Para recordar

  • Una IIFE se ejecuta al definirse y crea un ámbito privado.
  • El patrón módulo expone solo una interfaz y oculta el estado en una clausura.
  • Node envuelve cada fichero CommonJS en una función.
  • Lo exportado es module.exports; exports es solo una referencia inicial.
  • require() cachea los módulos.

Ejercicio

Añada al inventario un método remove(name, qty) que no permita dejar existencias negativas, y un contador privado de operaciones que se pueda consultar con un método operations().