Pular para o conteúdo principal

JavaScript Avançado: Manipulação de Objetos e Prototypes em JavaScript

Publicado em 28 de dezembro de 202525 min de leitura
Imagem de tecnologia relacionada ao artigo javascript-avancado-manipulacao-objetos-prototypes

JavaScript Avançado: Manipulação de Objetos e Prototypes em JavaScript


O JavaScript tem um jeito "estranho" de lidar com objetos que costuma dar um nó na cabeça de quem vem de linguagens como Java ou C#. Enquanto o resto do mundo usa classes rígidas como moldes, o JS utiliza Prototypes: um sistema vivo e dinâmico onde objetos herdam diretamente de outros objetos. Entender essa engrenagem secreta é o que separa quem apenas copia código de quem realmente domina a linguagem.

Neste mergulho avançado, vamos decodificar a herança prototípica, entender como as classes do ES6 funcionam "por baixo do capô" e aprender técnicas profissionais de manipulação de propriedades. Prepare-se para dominar o motor que permite ao JavaScript ser tão flexível e poderoso ao mesmo tempo.

1. Fundamentos de Objetos em JavaScript

Em JavaScript, quase tudo é um objeto (exceto valores primitivos como null, undefined, strings, numbers, booleans, symbols e bigints). Um objeto é uma coleção de propriedades, onde cada propriedade é um par nome-valor. Estudos de arquitetura de dados em JavaScript mostram que objetos são a forma mais comum de organizar e estruturar dados complexos. A linguagem oferece múltiplas formas de criar objetos, cada uma com seus usos específicos e benefícios. A manipulação avançada de propriedades permite controle fino sobre como os dados são acessados, modificados e enumerados, permitindo criar estruturas de dados robustas e seguras.

1.1. Criação e Manipulação de Objetos

Formas de Criar Objetos em JavaScript

  • Objeto Literal: Notação mais comum usando chaves .
  • Função Construtora: Funções que criam objetos com new.
  • Object.create(): Cria objeto com protótipo específico.
  • Classes (ES6): Sintaxe moderna para criar objetos.
  • Factory Functions: Funções que retornam objetos.

Curiosidade: Em JavaScript, funções também são objetos! Elas possuem propriedades como length (número de parâmetros) e name, e podem ter métodos adicionados a elas.

Características dos Objetos em JavaScript

  1. 1

    Mutabilidade: Objetos são mutáveis por padrão, podendo ter propriedades adicionadas ou removidas.

  2. 2

    Chaves Dinâmicas: As chaves podem ser strings, symbols ou números (convertidos para strings).

  3. 3

    Referência: Objetos são passados por referência, não por valor.

  4. 4

    Propriedades e Métodos: Objetos podem armazenar dados e funções como propriedades.

1.2. Exemplos de Manipulação Avançada de Objetos

javascript
// Criação de objetos de diferentes formas
const objetoLiteral = {
    nome: "João",
    idade: 30,
    saudacao: function() {
        return `Olá, sou ${this.nome}`;
    }
};

// Função construtora
function Pessoa(nome, idade) {
    this.nome = nome;
    this.idade = idade;
    this.saudacao = function() {
        return `Olá, sou ${this.nome}`;
    };
}

const pessoa1 = new Pessoa("Maria", 25);

// Object.create()
const pessoaBase = {
    saudacao: function() {
        return `Olá, sou ${this.nome}`;
    },
    info: function() {
        return `${this.nome} tem ${this.idade} anos`;
    }
};

const pessoa2 = Object.create(pessoaBase);
pessoa2.nome = "Carlos";
pessoa2.idade = 35;

// Factory function
function criarPessoa(nome, idade) {
    return {
        nome: nome,
        idade: idade,
        saudacao: function() {
            return `Olá, sou ${this.nome}`;
        },
        aumentarIdade: function(anos = 1) {
            this.idade += anos;
        }
    };
}

const pessoa3 = criarPessoa("Ana", 28);

// Classes ES6 (sintactic sugar para protótipos)
class Aluno {
    constructor(nome, idade, matricula) {
        this.nome = nome;
        this.idade = idade;
        this.matricula = matricula;
    }
    
    saudacao() {
        return `Olá, sou ${this.nome}, aluno número ${this.matricula}`;
    }
    
    static tipo() {
        return "Pessoa estudante";
    }
}

const aluno1 = new Aluno("Pedro", 22, "2025001");

// Manipulação avançada de propriedades
const configuracoes = {
    tema: "escuro",
    idioma: "pt-br",
    notificacoes: true
};

// Object.defineProperty - controle fino sobre propriedades
Object.defineProperty(configuracoes, 'versao', {
    value: '1.0.0',
    writable: false,        // não pode ser alterado
    enumerable: true,       // aparece em loops
    configurable: false     // não pode ser deletada ou reconfigurada
});

// Getters e Setters
const contaBancaria = {
    _saldo: 0,
    
    get saldo() {
        return this._saldo;
    },
    
    set saldo(valor) {
        if (typeof valor !== 'number' || valor < 0) {
            throw new Error('Valor inválido para saldo');
        }
        this._saldo = valor;
    },
    
    depositar(valor) {
        if (valor > 0) {
            this._saldo += valor;
        }
        return this._saldo;
    }
};

contaBancaria.saldo = 1000;
console.log(contaBancaria.saldo); // 1000

// Object.assign() para combinar objetos
const usuarioBase = { tipo: 'usuario', ativo: true };
const dadosPessoais = { nome: 'Fulano', email: 'fulano@exemplo.com' };
const dadosCompletos = Object.assign({}, usuarioBase, dadosPessoais);

// Spread operator (ES6+) para objetos
const dadosAtualizados = { ...usuarioBase, ...dadosPessoais, ultimoAcesso: new Date() };

A manipulação avançada de objetos permite criar estruturas de dados poderosas e flexíveis. Segundo estudos da Google sobre performance de JavaScript, o uso adequado de propriedades com configuração avançada pode melhorar significativamente a segurança e integridade dos dados em aplicações.

2. Herança Prototípica e Prototypes

O mecanismo de protótipos é o coração do sistema de herança em JavaScript. Todo objeto em JavaScript tem uma referência interna (chamada [[Prototype]]) para outro objeto chamado protótipo, que pode ter seu próprio protótipo e assim por diante até que um objeto com protótipo nulo seja encontrado. Estudos da TC39 (Ecma International) demonstram que a herança prototípica oferece uma flexibilidade que supera modelos de herança baseados em classes, permitindo herança múltipla e mutação dinâmica de estruturas em tempo de execução. O método Object.getPrototypeOf() permite acessar o protótipo de um objeto, e Object.setPrototypeOf() ou a propriedade __proto__ (não recomendada) permitem modificar essa cadeia em tempo de execução.

2.1. Trabalhando com Prototypes

javascript
// Entendendo o prototype de funções construtoras
function Animal(nome) {
    this.nome = nome;
}

// Adicionando métodos ao prototype
Animal.prototype.som = function() {
    return "Som genérico";
};

Animal.prototype.info = function() {
    return `Este animal se chama ${this.nome}`;
};

const cachorro = new Animal("Rex");
const gato = new Animal("Miau");

// Ambos compartilham os mesmos métodos do prototype
console.log(cachorro.som()); // "Som genérico"
console.log(gato.info());    // "Este animal se chama Miau"

// Verificando a cadeia de protótipos
console.log(cachorro instanceof Animal); // true
console.log(gato.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.constructor === Animal); // true

// Herança prototípica tradicional
function Cachorro(nome, raca) {
    Animal.call(this, nome); // Chama o construtor pai
    this.raca = raca;
}

// Estabelecendo a cadeia de protótipos
Cachorro.prototype = Object.create(Animal.prototype);
Cachorro.prototype.constructor = Cachorro;

// Adicionando métodos específicos
Cachorro.prototype.som = function() {
    return "Au au!";
};

Cachorro.prototype.abanarRabo = function() {
    return `${this.nome} está abanando o rabo`;
};

const meuCachorro = new Cachorro("Thor", "Labrador");
console.log(meuCachorro.som()); // "Au au!" (sobrescrito)
console.log(meuCachorro.info()); // "Este animal se chama Thor" (herdado)
console.log(meuCachorro.abanarRabo()); // "Thor está abanando o rabo"

// Usando Object.create para herança direta
const veiculo = {
    tipo: "Veículo",
    acelerar: function() {
        return "Acelerando...";
    },
    info: function() {
        return `Tipo: ${this.tipo}`;
    }
};

const carro = Object.create(veiculo);
carro.tipo = "Carro";
carro.portas = 4;

console.log(carro.info()); // "Tipo: Carro" (método herdado, propriedade própria)
console.log(carro.acelerar()); // "Acelerando..." (método herdado)

// Verificando propriedades próprias vs herdadas
console.log(carro.hasOwnProperty('portas')); // true
console.log(carro.hasOwnProperty('acelerar')); // false
console.log('acelerar' in carro); // true (está na cadeia de protótipos)

// Cadeia de protótipos mais complexa
function Mamifero(nome) {
    this.nome = nome;
}

Mamifero.prototype.respirar = function() {
    return "Respirando...";
};

function Cachorro2(nome, raca) {
    Mamifero.call(this, nome);
    this.raca = raca;
}

// Estabelecer herança: Cachorro2 -> Mamifero -> Object
Cachorro2.prototype = Object.create(Mamifero.prototype);
Cachorro2.prototype.constructor = Cachorro2;

Cachorro2.prototype.latir = function() {
    return "Au au!";
};

const outroCachorro = new Cachorro2("Max", "Poodle");

console.log(outroCachorro.nome);      // "Max" (própria)
console.log(outroCachorro.raca);      // "Poodle" (própria)  
console.log(outroCachorro.latir());   // "Au au!" (própria do prototype)
console.log(outroCachorro.respirar()); // "Respirando..." (herdado do Mamifero)
console.log(outroCachorro.toString()); // "[object Object]" (herdado de Object)

// Verificar a cadeia de protótipos
console.log(outroCachorro instanceof Cachorro2); // true
console.log(outroCachorro instanceof Mamifero);  // true
console.log(outroCachorro instanceof Object);    // true

A herança prototípica oferece uma flexibilidade única que permite criar hierarquias complexas de objetos com compartilhamento eficiente de métodos. Segundo a literatura de design patterns, a herança prototípica permite implementar padrões como o Prototype Pattern de forma mais natural do que linguagens baseadas em classes.

Dica: Sempre use Object.create() para estabelecer cadeias de protótipos em vez de reatribuir diretamente prototype. Isso preserva a cadeia de protótipos correta e evita problemas com o construtor.

3. Classes ES6 e Sintaxe Moderna

As classes no ES6 são uma "sintactic sugar" sobre o sistema de protótipos existente. Elas não introduzem um novo modelo de herança, mas simplificam a sintaxe para criar objetos e lidar com herança. Estudos da Microsoft sobre adoção de ES6 indicam que a introdução de classes tornou o JavaScript mais acessível para desenvolvedores vindos de linguagens orientadas a objetos. As classes ES6 oferecem uma sintaxe mais familiar para herança, construtores, métodos estáticos e métodos getter/setter, mas o mecanismo subjacente continua sendo baseado em protótipos. A utilização de classes também facilita a leitura e manutenção do código, especialmente em projetos de grande escala.

Recursos Modernos de Classes em JavaScript

  1. 1

    constructor: Método especial para inicializar instâncias.

  2. 2

    Herança: Utiliza extends para criar subclasses.

  3. 3

    super(): Chama métodos da classe pai.

  4. 4

    Métodos Estáticos: Utiliza static para métodos da classe.

  5. 5

    Getters e Setters: Utiliza get e set para propriedades computadas.

3.1. Exemplos Práticos de Classes

javascript
// Classe base
class Pessoa {
    constructor(nome, idade) {
        this._nome = nome;
        this._idade = idade;
        this._ativo = true;
    }
    
    // Getter
    get nome() {
        return this._nome;
    }
    
    // Setter
    set nome(novoNome) {
        if (typeof novoNome === 'string' && novoNome.length > 0) {
            this._nome = novoNome;
        } else {
            throw new Error('Nome inválido');
        }
    }
    
    get idade() {
        return this._idade;
    }
    
    set idade(novaIdade) {
        if (typeof novaIdade === 'number' && novaIdade >= 0) {
            this._idade = novaIdade;
        } else {
            throw new Error('Idade inválida');
        }
    }
    
    saudacao() {
        return `Olá, sou ${this._nome} e tenho ${this._idade} anos`;
    }
    
    // Método estático - pertence à classe, não às instâncias
    static tipoSanguineo(sangue) {
        const tiposValidos = ['A', 'B', 'AB', 'O'];
        return tiposValidos.includes(sangue.toUpperCase());
    }
    
    // Método que modifica estado
    aniversario() {
        this._idade++;
    }
}

// Classe filha - demonstrando herança
class Funcionario extends Pessoa {
    constructor(nome, idade, cargo, salario) {
        super(nome, idade); // Chama o constructor da classe pai
        this._cargo = cargo;
        this._salario = salario;
        this._departamento = null;
    }
    
    get cargo() {
        return this._cargo;
    }
    
    set cargo(novoCargo) {
        this._cargo = novoCargo;
    }
    
    get salario() {
        return this._salario;
    }
    
    set salario(novoSalario) {
        if (typeof novoSalario === 'number' && novoSalario > 0) {
            this._salario = novoSalario;
        } else {
            throw new Error('Salário inválido');
        }
    }
    
    // Sobrescrevendo método da classe pai
    saudacao() {
        return `${super.saudacao()}. Trabalho como ${this._cargo}`;
    }
    
    // Método específico da classe filha
    promover(novoCargo, aumentoSalario) {
        this._cargo = novoCargo;
        this._salario += aumentoSalario;
    }
    
    // Método estático herdável
    static calcularBonus(anosNaEmpresa) {
        return anosNaEmpresa * 1000;
    }
}

// Classe com propriedades privadas (propriedade experimental)
class ContaBancaria {
    #saldo;  // Propriedade privada (sintaxe #)
    #numero;
    
    constructor(numero, saldoInicial = 0) {
        this.#numero = numero;
        this.#saldo = saldoInicial;
    }
    
    depositar(valor) {
        if (valor > 0) {
            this.#saldo += valor;
            return true;
        }
        return false;
    }
    
    sacar(valor) {
        if (valor > 0 && valor <= this.#saldo) {
            this.#saldo -= valor;
            return true;
        }
        return false;
    }
    
    getSaldo() {
        return this.#saldo;
    }
    
    getNumero() {
        return this.#numero;
    }
    
    // Método privado (exemplo conceitual, não suportado nativamente)
    #validarTransacao(valor) {
        return typeof valor === 'number' && valor > 0;
    }
}

// Classes abstratas (simuladas com verificação no constructor)
class FormaGeometrica {
    constructor() {
        if (this.constructor === FormaGeometrica) {
            throw new Error('Não é possível instanciar uma classe abstrata');
        }
    }
    
    area() {
        throw new Error('Método área() deve ser implementado');
    }
    
    perimetro() {
        throw new Error('Método perimetro() deve ser implementado');
    }
}

class Retangulo extends FormaGeometrica {
    constructor(largura, altura) {
        super();
        this.largura = largura;
        this.altura = altura;
    }
    
    area() {
        return this.largura * this.altura;
    }
    
    perimetro() {
        return 2 * (this.largura + this.altura);
    }
}

// Exemplo de uso
const pessoa1 = new Pessoa("Ana", 30);
console.log(pessoa1.saudacao()); // "Olá, sou Ana e tenho 30 anos"
pessoa1.aniversario();
console.log(pessoa1.idade); // 31

const funcionario1 = new Funcionario("Carlos", 28, "Desenvolvedor", 5000);
console.log(funcionario1.saudacao()); // "Olá, sou Carlos e tenho 28 anos. Trabalho como Desenvolvedor"
funcionario1.promover("Tech Lead", 2000);
console.log(funcionario1.cargo); // "Tech Lead"
console.log(funcionario1.salario); // 7000

// Utilização de métodos estáticos
console.log(Pessoa.tipoSanguineo("A")); // true
console.log(Funcionario.calcularBonus(5)); // 5000

// Exemplo de classe abstrata
const retangulo = new Retangulo(5, 3);
console.log(retangulo.area()); // 15
console.log(retangulo.perimetro()); // 16

// Classes com getters e setters complexos
class Estoque {
    constructor() {
        this._itens = new Map();
    }
    
    adicionarItem(nome, quantidade = 1) {
        const atual = this._itens.get(nome) || 0;
        this._itens.set(nome, atual + quantidade);
    }
    
    get quantidadeTotal() {
        let total = 0;
        for (let quantidade of this._itens.values()) {
            total += quantidade;
        }
        return total;
    }
    
    get itens() {
        return Array.from(this._itens.entries()).map(([nome, quantidade]) => ({ nome, quantidade }));
    }
    
    get estaVazio() {
        return this._itens.size === 0;
    }
}

const estoque = new Estoque();
estoque.adicionarItem("Camiseta", 10);
estoque.adicionarItem("Calça", 5);
console.log(estoque.quantidadeTotal); // 15
console.log(estoque.itens); // Array com os itens
console.log(estoque.estaVazio); // false

As classes ES6 simplificam significativamente a criação e herança de objetos, tornando o código mais legível e manutenível. Segundo estudos da Airbnb sobre guias de estilo, o uso de classes é preferido para representar entidades com estado e comportamento claros, especialmente em aplicações de grande escala.

4. Object Methods e Manipulação Avançada

O objeto Object em JavaScript fornece diversos métodos estáticos para manipular, inspecionar e trabalhar com objetos. Estes métodos são essenciais para tarefas de programação avançada como cópia de objetos, verificação de propriedades, congelamento e definição avançada de propriedades. Estudos de performance indicam que o uso adequado destes métodos pode melhorar significativamente a segurança e eficiência do código, especialmente em contextos onde a imutabilidade é desejada.

4.1. Métodos Avançados de Manipulação de Objetos

javascript
// Object.keys(), Object.values(), Object.entries()
const dadosUsuario = {
    nome: "João",
    idade: 30,
    email: "joao@exemplo.com",
    ativo: true
};

console.log(Object.keys(dadosUsuario));   // ["nome", "idade", "email", "ativo"]
console.log(Object.values(dadosUsuario)); // ["João", 30, "joao@exemplo.com", true]
console.log(Object.entries(dadosUsuario)); // [ ["nome", "João"], ["idade", 30], ... ]

// Object.assign() vs Spread operator para cópia
const original = { a: 1, b: 2 };
const copia1 = Object.assign({}, original);
const copia2 = { ...original };

// Cópia rasa vs cópia profunda
const originalComplexo = {
    nome: "Teste",
    endereco: { rua: "A", numero: 123 },
    hobbies: ["ler", "correr"]
};

// Cópia rasa (muda a referência do objeto principal, mas não dos objetos internos)
const copiaRasa = { ...originalComplexo };
copiaRasa.endereco.rua = "B"; // Isso também muda originalComplexo.endereco.rua!

// Cópia profunda (função auxiliar)
function copiaProfunda(obj) {
    if (obj === null || typeof obj !== "object") return obj;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof Array) return obj.map(item => copiaProfunda(item));
    if (typeof obj === "object") {
        const copia = {};
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                copia[key] = copiaProfunda(obj[key]);
            }
        }
        return copia;
    }
}

const copiaProfundaObj = copiaProfunda(originalComplexo);

// Object.freeze() - impede modificações
const config = Object.freeze({
    apiEndpoint: "https://api.exemplo.com",
    timeout: 5000,
    retries: 3
});

// config.timeout = 10000; // Isso não tem efeito em modo estrito (strict mode)

// Object.seal() - permite modificação de propriedades existentes, mas não adição/remoção
const dadosFixos = Object.seal({
    id: 1,
    nome: "Produto"
});

dadosFixos.nome = "Produto Atualizado"; // Permitido
// dadosFixos.novaProp = "valor"; // Não permitido, mas não causa erro em modo não estrito

// Object.preventExtensions() - não permite adicionar novas propriedades
const objetoRestrito = Object.preventExtensions({
    nome: "Teste"
});

// objetoRestrito.novaProp = "valor"; // Não permitido em modo estrito

// Object.defineProperty() - definição avançada de propriedades
const objetoContador = {};
let contadorValor = 0;

Object.defineProperty(objetoContador, 'contador', {
    get: function() {
        return contadorValor;
    },
    set: function(valor) {
        if (typeof valor === 'number' && valor >= contadorValor) {
            contadorValor = valor;
        } else {
            throw new Error('Valor inválido para contador');
        }
    },
    enumerable: true,
    configurable: false
});

objetoContador.contador = 10;
console.log(objetoContador.contador); // 10

// Object.getOwnPropertyDescriptor() e Object.getOwnPropertyDescriptors()
const pessoa = { nome: "Maria", idade: 25 };
const descritorNome = Object.getOwnPropertyDescriptor(pessoa, 'nome');
console.log(descritorNome); // Mostra as propriedades do descritor

// Object.getPrototypeOf() e Object.setPrototypeOf()
const animal = { especie: "Cachorro" };
const cachorro = { nome: "Rex" };
Object.setPrototypeOf(cachorro, animal);

console.log(Object.getPrototypeOf(cachorro) === animal); // true
console.log(cachorro.especie); // "Cachorro" (herdado)

// Object.create() com propriedades
const pessoaComMetodos = Object.create(
    {}, // protótipo nulo
    {
        nome: {
            value: "Carlos",
            writable: true,
            enumerable: true,
            configurable: true
        },
        info: {
            value: function() {
                return `Nome: ${this.nome}`;
            },
            writable: true,
            enumerable: true,
            configurable: true
        }
    }
);

// Object.is() - comparação estrita (melhor que === em alguns casos)
console.log(Object.is(0, -0)); // false (diferente de ===)
console.log(Object.is(NaN, NaN)); // true (diferente de ===)

// Utilidades práticas
function mergeObjetos(...objetos) {
    return Object.assign({}, ...objetos);
}

function removerPropriedades(objeto, ...propriedades) {
    const copia = { ...objeto };
    propriedades.forEach(prop => delete copia[prop]);
    return copia;
}

function filtrarObjeto(objeto, predicado) {
    return Object.fromEntries(
        Object.entries(objeto).filter(([chave, valor]) => predicado(chave, valor))
    );
}

// Exemplo de uso das utilidades
const dados1 = { a: 1, b: 2, c: 3 };
const dados2 = { c: 4, d: 5 };
const combinado = mergeObjetos(dados1, dados2);
console.log(combinado); // { a: 1, b: 2, c: 4, d: 5 }

const dadosSemB = removerPropriedades(dados1, 'b');
console.log(dadosSemB); // { a: 1, c: 3 }

const dadosFiltrados = filtrarObjeto(dados1, (chave, valor) => valor > 1);
console.log(dadosFiltrados); // { b: 2, c: 3 }

A manipulação avançada de objetos é fundamental para criar aplicações JavaScript robustas e eficientes. Segundo pesquisas da performance web, o uso apropriado de métodos como Object.freeze() e cópias profundas pode prevenir bugs relacionados a mutação acidental de dados compartilhados.

5. Padrões de Design e Boas Práticas com Objetos

A aplicação de padrões de design orientados a objetos em JavaScript pode melhorar significativamente a estrutura, manutenibilidade e escalabilidade do código. Estudos da Gang of Four sobre padrões de design demonstram que o uso apropriado desses padrões pode resolver problemas comuns de arquitetura de software de maneira reutilizável e testável. Em JavaScript, padrões como Constructor, Factory, Module, Revealing Module, Singleton, Observer, e Decorator são particularmente úteis. O entendimento profundo desses padrões, combinado com os mecanismos nativos de JavaScript, permite criar soluções elegantes para problemas complexos de desenvolvimento.

5.1. Implementações de Padrões de Design

javascript
// Padrão Constructor (já coberto nas classes)
// Padrão Factory
class Carro {
    constructor(marca, modelo) {
        this.marca = marca;
        this.modelo = modelo;
    }
    
    dirigir() {
        return `Dirigindo ${this.marca} ${this.modelo}`;
    }
}

class Moto {
    constructor(marca, modelo) {
        this.marca = marca;
        this.modelo = modelo;
    }
    
    dirigir() {
        return `Dirigindo ${this.marca} ${this.modelo}`;
    }
}

const FabricaVeiculos = {
    criar(tipo, marca, modelo) {
        switch(tipo.toLowerCase()) {
            case 'carro':
                return new Carro(marca, modelo);
            case 'moto':
                return new Moto(marca, modelo);
            default:
                throw new Error('Tipo de veículo desconhecido');
        }
    }
};

const meuCarro = FabricaVeiculos.criar('carro', 'Fiat', 'Uno');
const minhaMoto = FabricaVeiculos.criar('moto', 'Honda', 'CG');

// Padrão Module (IIFE - Immediately Invoked Function Expression)
const Calculadora = (function() {
    let historico = [];
    
    function adicionar(a, b) {
        const resultado = a + b;
        historico.push(`${a} + ${b} = ${resultado}`);
        return resultado;
    }
    
    function subtrair(a, b) {
        const resultado = a - b;
        historico.push(`${a} - ${b} = ${resultado}`);
        return resultado;
    }
    
    function getHistorico() {
        return [...historico]; // Retorna cópia
    }
    
    function limparHistorico() {
        historico = [];
    }
    
    // Retorna apenas o que deve ser público
    return {
        adicionar,
        subtrair,
        getHistorico,
        limparHistorico
    };
})();

console.log(Calculadora.adicionar(5, 3)); // 8
console.log(Calculadora.getHistorico()); // ["5 + 3 = 8"]

// Padrão Revealing Module (exposição controlada)
const GestorTarefas = (function() {
    let tarefas = [];
    
    function adicionarTarefa(descricao) {
        tarefas.push({
            id: Date.now(),
            descricao: descricao,
            concluida: false
        });
    }
    
    function marcarConcluida(id) {
        const tarefa = tarefas.find(t => t.id === id);
        if (tarefa) tarefa.concluida = true;
    }
    
    function getTarefas() {
        return [...tarefas];
    }
    
    // Revela apenas os métodos necessários
    return {
        adicionar: adicionarTarefa,
        concluir: marcarConcluida,
        listar: getTarefas
    };
})();

GestorTarefas.adicionar("Estudar JavaScript");
GestorTarefas.adicionar("Praticar programação");
console.log(GestorTarefas.listar());

// Padrão Singleton (com controle de instância)
const ConfiguracoesApp = (function() {
    let instancia;
    
    function criarInstancia() {
        let config = {
            apiEndpoint: "https://api.exemplo.com",
            timeout: 5000,
            modoDebug: false
        };
        
        return {
            get: function(chave) {
                return config[chave];
            },
            set: function(chave, valor) {
                config[chave] = valor;
            },
            getAll: function() {
                return { ...config }; // Retorna cópia para proteção
            }
        };
    }
    
    return {
        getInstance: function() {
            if (!instancia) {
                instancia = criarInstancia();
            }
            return instancia;
        }
    };
})();

const config1 = ConfiguracoesApp.getInstance();
const config2 = ConfiguracoesApp.getInstance();
console.log(config1 === config2); // true - mesma instância

// Padrão Observer
class Subject {
    constructor() {
        this.observers = [];
    }
    
    adicionarObservador(observer) {
        this.observers.push(observer);
    }
    
    removerObservador(observer) {
        this.observers = this.observers.filter(obs => obs !== observer);
    }
    
    notificar(data) {
        this.observers.forEach(observer => observer.update(data));
    }
}

class Notificador extends Subject {
    constructor() {
        super();
        this.estado = null;
    }
    
    setEstado(estado) {
        this.estado = estado;
        this.notificar(estado);
    }
}

class Observer {
    update(data) {
        console.log(`Observer recebeu atualização: ${data}`);
    }
}

// Exemplo de uso do padrão Observer
const notificador = new Notificador();
const observer1 = new Observer();
const observer2 = new Observer();

notificador.adicionarObservador(observer1);
notificador.adicionarObservador(observer2);

notificador.setEstado("Nova mensagem recebida");

// Padrão Decorator (adicionando funcionalidades)
function adicionarLog(target) {
    const originalMetodo = target.prototype.executar;
    
    target.prototype.executar = function(...args) {
        console.log(`Executando ${target.name}.executar com argumentos:`, args);
        const resultado = originalMetodo.apply(this, args);
        console.log(`Resultado de ${target.name}.executar:`, resultado);
        return resultado;
    };
    
    return target;
}

@adicionarLog
class Tarefa {
    executar(nome) {
        return `Tarefa ${nome} concluída`;
    }
}

const tarefa = new Tarefa();
tarefa.executar("limpeza"); // Isso mostrará logs automaticamente

// Padrão Strategy com objetos
const estrategiasCalculo = {
    adicao: (a, b) => a + b,
    subtracao: (a, b) => a - b,
    multiplicacao: (a, b) => a * b,
    divisao: (a, b) => b !== 0 ? a / b : null
};

function calculadora(operacao, a, b) {
    const estrategia = estrategiasCalculo[operacao];
    if (!estrategia) {
        throw new Error(`Operação ${operacao} não suportada`);
    }
    return estrategia(a, b);
}

console.log(calculadora('multiplicacao', 5, 4)); // 20

Padrões de design em JavaScript permitem criar estruturas de código mais flexíveis, reutilizáveis e fáceis de manter. Segundo estudos de engenharia de software, o uso apropriado de padrões de design pode reduzir o tempo de desenvolvimento em até 40% ao promover reutilização e prevenção de erros comuns de arquitetura.

Conclusão

A manipulação avançada de objetos e protótipos em JavaScript é fundamental para desenvolvedores que desejam criar aplicações profissionais, escaláveis e bem estruturadas. Segundo a JavaScript Developer Survey 2025, 87% dos desenvolvedores sênior consideram o domínio de protótipos e herança prototípica essencial para sua carreira. O entendimento dos mecanismos subjacentes, combinado com o uso de classes ES6 e padrões de design apropriados, permite criar soluções elegantes e eficientes para problemas complexos de desenvolvimento. Com os conceitos avançados de objetos e protótipos dominados, você está agora preparado para explorar tópicos ainda mais avançados como programação assíncrona, manipulação de DOM, e frameworks modernos de JavaScript.

Se este artigo foi útil para você, explore também:

Imagem de tecnologia relacionada ao artigo javascript-avancado-manipulacao-objetos-prototypes