Closures: O Fundamento Invisível
Antes de explorar padrões avançados, precisamos entender que closures são funções que "lembram" do escopo em que foram criadas, mesmo após a função externa retornar. Em JavaScript, toda função cria um closure automático. Esse mecanismo é a base de tudo que você verá neste artigo.
function contador() {
let numero = 0;
return function incrementar() {
return ++numero; // Acessa 'numero' do escopo externo
};
}
const meuContador = contador();
console.log(meuContador()); // 1
console.log(meuContador()); // 2
console.log(meuContador()); // 3
// numero está protegido, não pode ser acessado diretamente
A variável numero existe em um escopo privado que apenas incrementar pode acessar. Isso é encapsulamento genuíno — não é apenas uma convenção, é garantido pelo motor JavaScript.
IIFE: Isolation Imediata
O que é IIFE?
IIFE (Immediately Invoked Function Expression) é uma função anônima executada no instante de sua definição. Ela cria um escopo isolado que impede poluição do escopo global e conflitos de variáveis.
(function() {
const varivelPrivada = "Apenas eu acesso isso";
console.log(varivelPrivada);
})();
// varivelPrivada não existe aqui
console.log(typeof varivelPrivada); // "undefined"
IIFE com Retorno e Parâmetros
IIFEs não servem apenas para isolamento — podem retornar objetos com métodos públicos enquanto mantêm dados privados:
const usuario = (function() {
const senha = "super_secreto"; // Privada
let tentativas = 0;
return {
login: function(senhaInformada) {
tentativas++;
if (senhaInformada === senha) {
return "Login bem-sucedido!";
}
return `Falha ${tentativas}. Tente novamente.`;
},
getTentativas: function() {
return tentativas;
}
};
})();
console.log(usuario.login("errada")); // Falha 1. Tente novamente.
console.log(usuario.login("super_secreto")); // Login bem-sucedido!
console.log(usuario.getTentativas()); // 2
console.log(usuario.senha); // undefined — realmente privada!
Module Pattern: Arquitetura Escalável
Estrutura Base
O Module Pattern combina closures e IIFE para criar módulos com API clara. Cada módulo encapsula lógica e expõe apenas o que é necessário:
const contaBancaria = (function() {
// PRIVADO
const contas = {};
const taxa = 0.02;
function calcularJuros(valor) {
return valor * (1 + taxa);
}
function validarConta(numero) {
return numero && numero.length === 10;
}
// PÚBLICO
return {
criarConta: function(numero, saldoInicial = 0) {
if (!validarConta(numero)) {
throw new Error("Número de conta inválido");
}
contas[numero] = { saldo: saldoInicial };
return `Conta ${numero} criada com R$${saldoInicial}`;
},
depositar: function(numero, valor) {
if (!contas[numero]) return "Conta não existe";
contas[numero].saldo += valor;
return `Novo saldo: R$${contas[numero].saldo}`;
},
sacar: function(numero, valor) {
if (!contas[numero]) return "Conta não existe";
if (contas[numero].saldo < valor) return "Saldo insuficiente";
contas[numero].saldo -= valor;
return `Saque realizado. Saldo: R$${contas[numero].saldo}`;
},
aplicarJuros: function(numero) {
if (!contas[numero]) return;
contas[numero].saldo = calcularJuros(contas[numero].saldo);
},
consultarSaldo: function(numero) {
return contas[numero]?.saldo ?? "Conta não encontrada";
}
};
})();
contaBancaria.criarConta("1234567890", 1000);
console.log(contaBancaria.depositar("1234567890", 500)); // Novo saldo: R$1500
contaBancaria.aplicarJuros("1234567890");
console.log(contaBancaria.consultarSaldo("1234567890")); // 1530
console.log(contaBancaria.taxa); // undefined — privado!
Vantagens do Module Pattern
O padrão acima alcança três objetivos críticos: os dados (contas, taxa) são absolutamente privados; funções auxiliares (calcularJuros, validarConta) não poluem o escopo global; e apenas a API intencional (criarConta, depositar, etc.) é exposta. Não é possível acessar ou modificar dados internos sem passar pelos métodos públicos.
Encapsulamento Real vs. Falso
Diferenças Práticas
Muitos desenvolvedores confundem privacidade com convenção. Um underscore (_variavel) é apenas uma sugestão — não impede acesso real:
// Falso encapsulamento (convenção)
class Pessoa {
constructor(nome) {
this._nome = nome; // Convenção: "é privado"
}
getNome() {
return this._nome;
}
}
const p = new Pessoa("João");
console.log(p._nome); // "João" — consegui acessar!
p._nome = "Hacker"; // Consegui modificar!
console.log(p.getNome()); // "Hacker"
Compare com encapsulamento genuíno usando closures:
// Encapsulamento real
function Pessoa(nome) {
let _nome = nome; // Realmente privado — sem underscore necessário
this.getNome = function() {
return _nome;
};
this.setNome = function(novoNome) {
if (novoNome && novoNome.length > 0) {
_nome = novoNome;
}
};
}
const p = new Pessoa("João");
console.log(p._nome); // undefined — não existe
console.log(p.getNome()); // "João"
p._nome = "Hacker"; // Cria propriedade nova, não afeta _nome real
console.log(p.getNome()); // "João" — imutável!
p.setNome("Maria");
console.log(p.getNome()); // "Maria" — mudou apenas através do setter
A diferença é fundamental: closures criam verdadeira privacidade no nível de linguagem, não apenas em convenção.
Conclusão
Você aprendeu três conceitos essenciais: closures são o mecanismo que permite que funções acessem variáveis de seus escopos externos, criando a base para privacidade real em JavaScript. IIFEs isolam código imediatamente, evitando conflitos globais e permitindo retornar APIs públicas com dados privados. O Module Pattern escala esses conceitos para criar arquiteturas mantíveis com encapsulamento genuíno, onde dados internos são absolutamente protegidos e apenas a interface intencional é exposta. Dominar esses padrões transforma você de programador que escreve código em engenheiro que projeta sistemas robustos.