#8 Desvendando o JavaScript Herança #iniciante

Tempo de leitura: 5 minutos

Continuando nossa série desvendando o JavaScript, damos inicio ao nosso oitavo e ultimo post, se você ainda você não leu os posts anteriores da série volte e leia!! irá ajudar você a compreender os conceitos que serão abordados e conhecer um pouco mais sobre a história do JavaScript.

Vamos agora falar um pouco mais sobre  Herança  no mundo JavaScript , os conceitos mais usados e como eles funcionam, para fechar com chave de ouro nossa série.

Herança

Diferente das linguagens mais conhecidas, como Java ou C++ que utilizam a orientação a objetos clássica, JavaScript utiliza uma abordagem diferente para compartilhar código entre entidades, chamada de orientação a protótipo. Mas antes de entrarmos em detalhes, vamos primeiro relembrar o que é a herança clássica nas linguagens orientadas a objetos.

Herança clássica

Herança em OOP (programação orientada a objetos) é a capacidade de classes compartilharem atributos e métodos entre si. Geralmente a herança é usada para compartilhar comportamentos generalizados e comuns entres as classes filhas.

Herança em JavaScript

Existem diversos modos de se implementar herança em JavaScript.

Neste post, falaremos um pouco das seguintes maneiras:

  • Prototype-chaining Inheritance
  • Parasitic Combination Inheritance
  • Functional Inheritance

Prototype-chaining Inheritance

Como vimos no post #2 em JavaScript não existem classes e sim objetos. Até mesmo as function são objetos. Se quisermos herdar os métodos e atributos de um objeto, o utilizamos como protótipo do novo objeto a ser criado. Mesmo que não esteja definido explicitamente no código, todos os outros objetos de JavaScript, com a exceção do objeto Object, utilizam um outro objeto como protótipo.

Vamos representar este conceito via código para ficar mais fácil de entender

var Pessoa = function(nome, email){
    this.nome = nome;
    this.email = email
};

Pessoa.prototype.fala = function(){
console.log("Olá, meu nome é "+this.nome+" e meu email é "+this.email);
};

Parasitic Combination Inheritance

Nós já vimos que, ao utilizar a Prototype-chaining Inheritance pura, a cada vez que instanciamos uma PessoaFisica, chamamos duas vezes a função Pessoa. Será que não há um modo mais performático de se implementar herança?

Existe sim, mas antes de entendermos como ele é implementado, precisamos entender a função
Object.create, escrita por Douglas Crockford, que usaremos em nossa implementação.

Essa é a função, que foi incluída ao ECMAScript 5:

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

Basicamente o que ela faz é criar uma função construtora F, setar o protótipo desta para o objeto passado como parâmetro e retornar uma instância de F.

Ou seja, ela devolve um objeto vazio que herda (possui a propriedade proto igual ao) objeto passado!

// Cria um objeto vazio com prototype igual ao de Pessoa
var pessoa = Object.create(Pessoa.prototype);

Note que, dessa forma, deixamos de dar new em uma Pessoa sem argumentos para criar um objeto vazio com o protótipo de Pessoa.

Mas nós não queremos uma pessoaFisica que é igual a uma pessoa! Queremos que nosso objeto saiba dizer seu CPF, certo?

Para isso, você poderia pensar em adicionar direto no objeto retornado:

var pessoaFisica = Object.create(Pessoa.prototype);
pessoaFisica.dizCpf = function(){
    console.log("meuCPF");
}

Mas assim nós acabamos de perder a vantagem do prototype: vamos definir uma função para cada instância de PessoaFisica.

O que queremos, então, é definir os métodos da PessoaFisica e da Pessoa utilizando seus prototypes e combiná-los de um modo mais enxuto.
Seria bacana uma função que encapsulasse todo esse comportamento, onde você passasse a função pai e a função filha e ela se responsabilizasse por fazer a função filha herdar a função pai, ou seja, combinar seus protótipos!

herda(Pessoa, PessoaFisica); // faz PessoaFisica herdar Pessoa
Legal! Então vamos implementar essa função:

var herda = function(mae, filha){

    // Faz uma cópia do prototipo da mãe
    var copiaDaMae = Object.create(mae.prototype);

    // herda mãe
    filha.prototype = copiaDaMae; 

    //Ajusta construtor da filha    
    filha.prototype.constructor = filha;
}

Agora, para herdarmos Pessoa, basta chamar a função herda passando a PessoaFisica e a Pessoa:

var PessoaFisica = function(nome, email, cpf){
    Pessoa.call(this, nome, email);
    this.cpf = cpf;
};

herda(Pessoa, PessoaFisica);

E, em seguida, definir os métodos da PessoaFisica em seu prototype:

var PessoaFisica = function(nome, email, cpf){
    Pessoa.call(this, nome, email);
    this.cpf = cpf;
};

herda(Pessoa, PessoaFisica);

PessoaFisica.prototype.dizCpf = function(){
    console.log(this.cpf);
};

Agora, para usar uma PessoaFisica, basta instanciá-la:

var joao = new PessoaFisica("Joao", "[email protected]", "meucpf");
joao.fala(); // Olá, meu nome é Joao e meu email é [email protected]
joao.dizCpf(); //meucpf

Functional Inheritance

Imagine que, em vez de termos uma função construtora e darmos new nela para obtermos um objeto, nós simplesmente retornássemos um objeto utilizando notação literal (esse padrão é explicado aqui):

Mas como vamos implementar herança sem utilizarmos prototypes?

Para isso, vamos utilizar uma estratégia um pouco diferente: em vez de adicionarmos os atributos e métodos nas instâncias de pessoaFisica, vamos fazer a função pessoaFisica() devolver uma pessoa() modificada:

var pessoaFisica = function(nome, email, cpf){
    var pessoa = pessoa(nome, email);
    pessoa.dizCpf = function(){
        console.log(cpf);
    };
    return pessoa;
}

Uma observação importante: quando utilizada a Funcional Inheritance, em nenhum momento você vai utilizar o operador new, sendo assim, suas funções não deverão ter nada atribuido à referência this.

Caso você utilize o this dentro de uma função e a chame sem o uso da palavra chave new, o this apontará para window, criando variáveis globais!

É isso ai pessoal espero que tenham gostado da nossa série desvendando o JavaScript #iniciante tentamos abordar os temas mais comuns que são a base da linguagem JavaScript. Voltaremos nos próximos posts com temas como TypeScript , ECMAScript , Docker, node e afins abordando desde os tópicos mais básicos até aos avançados.

gostou do conteúdo? não deixe de seguir a uebile nas redes sociais, pois toda semana tem post novo aqui no blog com mais dicas para o seu impulso digital.