Prototypes - Desvendando Herança em JavaScript
Tags: javascript, programming, oop, webdev
Published: 2023-08-03 19:31:17

## Introdução: A Ancestralidade do Código

Sabe o que eu acho mais interessante sobre programação? É que nela nós encontramos conceitos da vida real aplicados de uma maneira peculiar, embora nada diferente da realidade. Um desses conceitos é herança. 

Na Orientação a Objetos, a herança é um dos princípios mais importantes para este paradigma tão adotado por programadores ao redor do mundo. Ela nos permite reutilizar atributos e métodos de classes, sem a necessidade de recriá-los a todo momento. 

Seguindo essa abordagem, estaremos propensos a atingir estes seis propósitos:

1. **Reusabilidade de código**
2. **Modularização**
3. **Polimorfismo**
4. **Abstração**
5. **Encapsulamento**
6. **Estrutura hierárquica**

Podemos nos aprofundar em cada um desses propósitos acima em outro momento, pois nosso foco aqui é no conceito de herança como um todo. Visualmente falando, a herança se parece com isso:


![Diagrama de classes representando herança](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/em2mtip7tpn0yvlqgl7b.png)

No exemplo acima, temos a superclasse Pessoa, que possui seus atributos e métodos. As subclasses Aluno e Professor herdam os atributos e métodos da superclasse, e ainda possuem seus próprios comportamentos. 
Essa é a essência do conceito de herança em programação: concentrar comportamentos e atributos comuns a uma entidade (geralmente uma classe ou objeto) e compartilhá-los por diferentes instâncias de entidades do mesmo tipo.

##Herança e o JavaScript
Se você possui background em linguagens *class-based* (Java ou C++), poderá achar um pouco confusa a maneira com que o JS lida com herança. Acontece que o mecanismo nativo da linguagem para lidar com este princípio é o que chamamos de *Prototypal Inheritance*, ou, em bom português, **herança prototípica**. 

Contudo, nosso querido JavaScript é uma linguagem que sofreu muitas alterações ao longo do tempo, garantindo aos desenvolvedores uma grande flexibilidade e versatilidade no momento de implementar suas funcionalidades. Se tratando do conceito de herança, existem algumas maneiras diferentes de aplicá-la no seu código. 

Com o advento do ECMAScript 2015 (ES6), foi introduzida à linguagem a sintaxe de classes, de modo análogo ao que encontramos nas linguagens OO. Assim, temos uma implementação de herança um pouco mais tradicional:

`class Person {
  constructor(name, age, email) {
    this.name = name;
    this.age = age;
    this.email = email;
  }

  talk() {
    console.log("Hello world!");
  }
}

class Student extends Person {
  constructor(course, absences, name, age, email) {
    super(name, age, email);
    this.course = course;
    this.absences = absences;
  }

  learn() {
    console.log("learning");
  }
}

// Criando um novo objeto do tipo Person
const person1 = new Person("John", 30, "john@example.com");

// Criando um novo objeto do tipo Student
const student1 = new Student("Math", 5, "Alice", 25, "alice@example.com");

// Acessando atributos e métodos
console.log(person1.name);        // Output: "John"
console.log(student1.course);     // Output: "Math"
person1.talk();                   // Output: "Hello world!"
student1.talk();                  // Output: "Hello world!"
student1.learn();                 // Output: "learning"`

Não há segredos aqui. Usamos a keyword extends para indicar que nossa classe herdará comportamentos e atributos da superclasse. Porém, o foco deste artigo será a Prototypal inheritance, a abordagem nativa, que veremos daqui para frente.  

##Desvendando os Prototypes
Você provavelmente já se deparou com a afirmação de que “tudo no JavaScript é objeto”. Sim, quase tudo. Acontece que o JavaScript trata os tipos primitivos como objetos, isso porque conseguimos acessar propriedades e métodos daquele tipo, da mesma forma que fazemos com um objeto literal:

`const num = 100;
console.log(num.toString()); // Output: "100"

const str = "Hello";
console.log(str.length); // Output: 5

const bool = true;
console.log(bool.valueOf()); // Output: true`

Mas se você tiver uma percepção um pouco mais aguçada, poderá se perguntar: Ora, se os tipos primitivos são objetos, e acessamos propriedades de objetos usando a sintaxe de `nomeDaVariavel.funcao()`, como então conseguimos ter acesso aos métodos destes tipos primitivos se eles não estão declarados de forma literal?

A resposta para esta pergunta reside no conceito de Prototypes. Todos os objetos possuem uma propriedade escondida chamada Prototype, que nada mais é do que uma referência a outro objeto (que leva o nome de Prototype), no qual residem todas as propriedades que serão compartilhadas por todas as instâncias daquele construtor. É precisamente aí que acontece herança em JavaScript, pois os objetos sempre herdam propriedades e métodos do seu Prototype.

Todos os tipos primitivos (com exceção de null e undefined) possuem seus respectivos Prototypes, ou se preferir, seus objetos globais que compartilham suas propriedades. Para entendermos melhor, podemos usar o console do browser para debugar e enxergar os prototypes em ação: