Tutoriais sobre Informática e Tecnologias

JavaScript

Herança e protótipos em Java Script

Em JavaScript, a herança e o sistema de protótipos são conceitos centrais para o comportamento orientado a objetos da linguagem. Diferente de outras linguagens como Java ou C#, que utilizam herança baseada em classes, o JavaScript implementa a herança baseada em protótipos, o que o torna único e mais flexível. Neste post, vamos entender o que são herança e protótipos em Java Script, e veremos alguns exemplos práticos.

O que é Herança?

Herança é um princípio da programação orientada a objetos que permite que um objeto derive propriedades e métodos de outro objeto. Isso ajuda a reutilizar código, evitando a duplicação de funcionalidades comuns.

Em linguagens baseadas em classes, a herança é feita por classes que estendem outras classes. Já no JavaScript, a herança é feita através de protótipos, que são uma forma de objetos herdarem características de outros objetos.

O que são Protótipos?

Todo objeto em JavaScript tem uma referência interna chamada [[Prototype]], que aponta para outro objeto, conhecido como protótipo. Este protótipo pode ter outro protótipo, formando uma cadeia de protótipos conhecida como cadeia de protótipos (prototype chain).

A grande vantagem dos protótipos é que, se uma propriedade ou método não for encontrado diretamente no objeto, o JavaScript procurará no protótipo desse objeto, e assim por diante, até encontrar a propriedade ou chegar ao final da cadeia.

Como funciona a Herança em JavaScript?

Em JavaScript, você pode criar novos objetos herdando propriedades e métodos de outros objetos. A herança é implementada através da propriedade especial prototype em funções e classes.

Exemplo básico de herança via protótipo:

// Definindo um objeto "Pessoa"
function Pessoa(nome, idade) {
  this.nome = nome;
  this.idade = idade;
}

// Adicionando um método ao protótipo de "Pessoa"
Pessoa.prototype.falar = function() {
  console.log(`Meu nome é ${this.nome} e eu tenho ${this.idade} anos.`);
};

// Criando uma nova instância de "Pessoa"
const pessoa1 = new Pessoa("João", 30);

// Usando o método herdado via protótipo
pessoa1.falar(); // Saída: Meu nome é João e eu tenho 30 anos.

Nesse exemplo, o método falar foi adicionado ao protótipo de Pessoa. Isso significa que todas as instâncias de Pessoa (como pessoa1) têm acesso a esse método.

A cadeia de Protótipos

A cadeia de protótipos é o mecanismo pelo qual JavaScript resolve propriedades e métodos não encontrados diretamente em um objeto. Se você tentar acessar uma propriedade em um objeto e ela não existir, o JavaScript vai procurar essa propriedade no protótipo do objeto, depois no protótipo do protótipo, e assim por diante.

Exemplo da cadeia de protótipos:

// Definindo um objeto "Animal"
function Animal(tipo) {
  this.tipo = tipo;
}

// Adicionando um método ao protótipo de "Animal"
Animal.prototype.comer = function() {
  console.log(`${this.tipo} está comendo.`);
};

// Definindo um objeto "Cachorro" que herda de "Animal"
function Cachorro(nome) {
  this.nome = nome;
  Animal.call(this, 'Cachorro'); // Chama o construtor de Animal
}

// Herdando o protótipo de "Animal"
Cachorro.prototype = Object.create(Animal.prototype);

// Adicionando um método ao protótipo de "Cachorro"
Cachorro.prototype.latir = function() {
  console.log(`${this.nome} está latindo.`);
};

// Criando uma instância de "Cachorro"
const dog = new Cachorro("Rex");

// Chamando métodos herdados e próprios
dog.comer(); // Saída: Cachorro está comendo.
dog.latir(); // Saída: Rex está latindo.

Nesse exemplo, Cachorro herda o método comer de Animal, utilizando a cadeia de protótipos. Isso acontece porque o protótipo de Cachorro é uma instância de Animal.

Protótipos vs Classes (ES6)

Com o lançamento do ES6, o JavaScript introduziu a sintaxe de classes, que fornece uma maneira mais amigável e concisa de trabalhar com herança e objetos. No entanto, é importante notar que, por baixo dos panos, as classes em JavaScript ainda utilizam protótipos.

Exemplo de herança usando classes ES6:

class Animal {
  constructor(tipo) {
    this.tipo = tipo;
  }

  comer() {
    console.log(`${this.tipo} está comendo.`);
  }
}

class Cachorro extends Animal {
  constructor(nome) {
    super('Cachorro'); // Chama o construtor da classe pai (Animal)
    this.nome = nome;
  }

  latir() {
    console.log(`${this.nome} está latindo.`);
  }
}

const dog = new Cachorro("Rex");
dog.comer(); // Saída: Cachorro está comendo.
dog.latir(); // Saída: Rex está latindo.

A sintaxe de classes oferece uma maneira mais intuitiva de escrever código orientado a objetos em JavaScript, mas o conceito de protótipos permanece inalterado.

Propriedade prototype e métodos como Object.create

Além da herança via construtores e classes, JavaScript também oferece a função Object.create para criar novos objetos com protótipos específicos.

Exemplo usando Object.create:

const animal = {
  tipo: 'Desconhecido',
  comer() {
    console.log(`${this.tipo} está comendo.`);
  }
};

const cachorro = Object.create(animal);
cachorro.tipo = 'Cachorro';
cachorro.latir = function() {
  console.log('O cachorro está latindo.');
};

cachorro.comer(); // Saída: Cachorro está comendo.
cachorro.latir(); // Saída: O cachorro está latindo.

Nesse exemplo, cachorro herda do protótipo animal, e você pode adicionar novos métodos e propriedades diretamente a cachorro.

Vantagens da Herança por Protótipos

  • Flexibilidade: Qualquer objeto pode ser usado como protótipo de outro, o que permite maior flexibilidade e reutilização de código;

  • Performance: Objetos criados a partir de protótipos compartilham métodos, economizando memória;

  • Customização: É possível adicionar ou modificar propriedades e métodos em protótipos a qualquer momento.

Outros exemplos

Protótipo é o mecanismo pelo qual os objetos em JavaScript herdam recursos uns dos outros através de cadeias de protótipos:

 const chevrolet = {
    modelo: 'Celta',
    velMax: 200
 }
 
 cont volkswagen = {
    modelo: 'Fox 2007',
    velMax: 180
 }
 
 /* com __proto__ (underline, underline proto underline, underline) acessamos o 
 protótipo, super objeto ou o objeto pai do objeto volkswagen */
 console.log(volkswagen.__proto__)

Significa que, se não for encontrado um atributo específico no objeto volkswagen, será procurado dentro do protótipo ou no objeto pai.

Se não achar, busca no protótipo do protótipo e assim por diante (cadeia de protótipos) até encontrar. Se não encontrar retorna undefined:

const avo = { attr1: 'X' }
const pai = { __proto__: avo, attr2: 'Y' } //cria outro atributo no protótipo 'avo' 
const filho = { __proto__: pai, attr3: 'Z' } //cria outro atributo no protótipo 'pai'
  
console.log(filho.attr1) //imprime 'X', pois o atributo foi encontrado no protótipo herdado 'avo'

Se tentarmos acessar um atributo que não esteja em nenhum protótipo, mas um atributo criado utilizando a estrutura Object.prototype.atributo = valor, será possível acessá-lo, ja que o protótipo ‘avo’ aponta naturalmente para Object.prototype:

 Object.prototype.attr4 = 'H'
 const avo = { attr1: 'X' }
 console.log(filho.attr4) //imprime 'H'

Para estabelecer uma relação de protótipo entre dois objetos utilizamos a função setPrototypeOf():

 const carro = {
   velAtual: 0,
   velMax: 250,
   acelerar(delta) {
     if(this.velAtual + delta <= this.velMax) {
        this.velAtual += delta
     } else {
        this.velAtual = this.velMax
     }
   },
   status() {
     return `${this.velAtual}Km/h de ${this.velMax}Km/h`
   }
 }
 
 const celta = {
    modelo: 2010,
    velMax: 200,
    status() {
      //super chama o método status() do protótipo (carro), não deste objeto this
       return `${this.modelo}: ${super.status()}` 
    }   
 } 
     
/* foi estabelecida uma relação entre o objeto 'celta' e o protótipo 'carro',
 ou seja, 'celta' tem 'carro' como seu protótipo ('celta' herda de 'carro')*/
 Object.setPrototypeOf(celta, carro)
 
 console.log(celta) // imprime { modelo: 2010, velMax: 200 }

 celta.acelerar(110) //chama método 'acelerar' do protótipo 'carro' passando parâmetro
 console.log(celta.status()) //imprime 2010: 110Km/h de 200Km/h 

O método Object.create() cria um novo objeto, utilizando um outro objeto existente como protótipo para o novo objeto a ser criado:

 const pai = { nome: 'José', idade: 32 }
 const filha = Object.create(pai) //cria novo objeto tendo como protótipo o objeto 'pai'
 
 filha.nome = 'Ana' //altera-se o nome
 
 //cria objeto e o faz herdar de 'pai'
 const filha2 = Object.create(pai, {
    //atributo que não pode ser editado (writable: false) mas pode ser listado (enumerable: true)
    nome: { value: 'Marcelo', writable: false, enumerable: true } 
    })
    
 //imprime o conteúdo do objeto 'filha2' e do objeto herdado 'pai':
 for(let key in filha2) {
    console.log(key) 
 }
 
 //imprime o conteúdo do objeto 'filha2' e do objeto herdado 'pai', mas mostra se o atributo foi herdado ou não:
 for(let key in filha2) {
    filha2.hasOwnProperty(key) ? //verifica se o atributo é do objeto ou foi herdado do protótipo ou da cadeia de protótipos
       console.log(key) : console.log(`Por herança: ${key}`) 
 }

/* Imprimirá: 
 nome
 Por herança: idade
*/ 

Toda função em JavaScript tem um atributo chamado “prototype”. Exemplos:

 //reverter uma string
 String.prototype.reverse = function () {
    //quebra a string, armazena os caracteres em um array revertendo a ordem e junta novamente 
    return this.split('').reverse().join('')
 }
 
 console.log('CriandoBits'.reverse())//imprime stiBodnairC
 
 //retorna a primeira posição de um array:
 Array.prototype.first = function() {
    return this[0]
 }
 
 console.log([3, 5, 5, 9].first()) //imprime 3 

A herança por protótipos é um conceito poderoso e central para o JavaScript. Embora possa parecer estranho para quem vem de linguagens baseadas em classes, ele oferece uma maneira mais flexível e eficiente de trabalhar com objetos.

Com a introdução das classes em ES6, o JavaScript tornou-se mais acessível para programadores acostumados com herança baseada em classes, mas a essência da herança por protótipos permanece como o coração da linguagem.

QUER SER UM PROGRAMADOR FULL-STACK E DOMINAR AS PRINCIPAIS TECNOLOGIAS DO MERCADO?

Aprenda através de projetos reais e aulas práticas. São 20 cursos completos + cursos bônus. Grupos privados exclusivos, atualizações constantes e lives semanais.

Python, PHP, Java Script, CSS, Node, Angular JS, MySQL, Photoshop, Flutter, AWS, Apache e muito mais!

CLIQUE NA IMAGEM ABAIXO E CONFIRA MAIS DETALHES:

Link do curso: https://go.hotmart.com/X68198266R

Dúvidas ou sugestões sobre herança e protótipos em Java Script? Deixem nos comentários! Para mais dicas, acesse o nosso canal no YouTube:
https://youtube.com/criandobits

Bene Silva Júnior

Bacharel em Sistemas de Informação pelo Instituto Paulista de Pesquisa e Ensino IPEP. Apaixonado por tecnologias e games do tempo da vovó!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *