# 7.8.4 - Classes e Objetos

## 🧬 Herança em JavaScript — O Poder de Reaproveitar Código!

A **herança** permite que um objeto **"pegue emprestado" propriedades e comportamentos de outro**. Isso evita repetição de código e ajuda a organizar melhor nossos programas. Em JavaScript, temos um jeito especial de fazer isso: **herança prototipada**.

> 💡 "Herança" é quando um **objeto ou classe "filha"** ganha tudo o que a **classe "pai"** tem — como poderes sendo passados de geração em geração! 🧙‍♂️➡️🧙‍♀️

***

### 🧱 Herança Prototipada: Como funciona?

No JavaScript, **objetos herdam de outros objetos por meio de protótipos**. Essa é a famosa **cadeia de protótipos (prototype chain)**.

Quando tentamos acessar uma propriedade/método, o JS:

1. Procura no próprio objeto.
2. Se não encontrar, sobe para o protótipo (`__proto__`).
3. Continua subindo na cadeia até encontrar ou chegar no `null`.

```javascript
const animal = {
  tipo: "Animal",
  fazerSom() {
    console.log("Som genérico...");
  }
};

const cachorro = Object.create(animal);
cachorro.fazerSom(); // Som genérico...
```

> 🧠 `Object.create()` cria um novo objeto que herda de outro.

***

### 🧪 Exemplo com função construtora

**📌 Herdando com e sem parâmetros**

```javascript
function Pessoa(nome) {
  this.nome = nome;
  this.falar = function () {
    console.log("Olá! Meu nome é " + this.nome);
  };
}

function Professor(nome, materia) {
  Pessoa.call(this, nome); // ⚠️ Chama o construtor pai (Pessoa) com o contexto do filho
  this.materia = materia;
}
```

**🔁 Definindo o protótipo e a referência do construtor**

```javascript
Professor.prototype = Object.create(Pessoa.prototype);
Professor.prototype.constructor = Professor; // 🛠 Corrige o ponteiro do construtor
```

**📎 Sobrescrevendo um método**

```javascript
Professor.prototype.falar = function () {
  console.log("Sou o professor " + this.nome + ", e ensino " + this.materia);
};

const prof = new Professor("Carlos", "Matemática");
prof.falar(); // "Sou o professor Carlos, e ensino Matemática"
```

> ⚠️ **Importante:** Sempre que usar `Object.create()` para herdar, **lembre de corrigir a referência `constructor`**.

***

### 🧠 **Observação importante: cuidado com o `Object.create()` e o `constructor`!**

Quando usamos `Object.create()` para criar herança entre objetos ou funções construtoras, estamos dizendo que o novo objeto deve **herdar o protótipo** de outro.

👀 **Mas tem um detalhe importante que MUITA gente esquece:**

> O `constructor` do novo protótipo **continua apontando para o construtor original (pai)** — não para o filho! ⚠️

Vamos ver isso na prática:

```javascript
function Pessoa(nome) {
  this.nome = nome;
}

function Professor(nome, materia) {
  Pessoa.call(this, nome);
  this.materia = materia;
}

// Herança
Professor.prototype = Object.create(Pessoa.prototype);

console.log(Professor.prototype.constructor); 
// 👉 Resultado: function Pessoa(nome) {...} ❌
```

➡️ Isso acontece porque `Object.create(Pessoa.prototype)` cria um novo objeto cujo `__proto__` é `Pessoa.prototype`. Então, o `constructor` é herdado de lá também.

***

#### 🛠️ Como corrigir?

Basta **redefinir manualmente** o `constructor` da função filha:

```javascript
Professor.prototype.constructor = Professor;
```

Agora tudo fica certo:

```javascript
console.log(Professor.prototype.constructor);
// 👉 Resultado: function Professor(nome, materia) {...} ✅
```

***

#### 📌 Por que isso é importante?

O `constructor` é usado por ferramentas, IDEs e o próprio JavaScript para entender **qual função criou um objeto**. Se essa referência estiver errada, pode causar:

* Confusão na hora de debugar 🔍
* Problemas em frameworks que usam `constructor` automaticamente 🔧
* Dificuldade na manutenção e leitura do código 😵‍💫

***

#### ✅ Resumo rápido

| Situação               | O que acontece                    | Como resolver                                                    |
| ---------------------- | --------------------------------- | ---------------------------------------------------------------- |
| Usou `Object.create()` | `constructor` aponta para o pai ❌ | Redefina manualmente com `Filho.prototype.constructor = Filho` ✅ |

***

### 🧠 **Observação: Entendendo `Professor.prototype`, `Professor.constructor` e `Professor.prototype.constructor`**

Essas três estruturas aparecem MUITO quando estamos lidando com **herança e protótipos** em JavaScript. Vamos entender cada uma delas de forma clara, com comparações e exemplos simples:

***

#### 🧱 `Professor.prototype`

É o **protótipo dos objetos que serão criados** quando usamos `new Professor()`.\
Tudo o que colocamos aqui será **compartilhado entre as instâncias** criadas com essa função.

```javascript
function Professor(nome) {
  this.nome = nome;
}
Professor.prototype.ensinar = function () {
  console.log("Ensinando...");
};

const prof = new Professor("Ana");
prof.ensinar(); // Ensinando...
```

***

#### 🧾 `Professor.constructor`

Isso se refere ao **construtor da função em si**, ou seja, `Function`!\
Lembre-se: em JavaScript, **funções também são objetos** — e o construtor das funções é `Function`.

```javascript
console.log(Professor.constructor === Function); // true ✅
```

***

#### 🧬 `Professor.prototype.constructor`

Aqui está o ponto chave!\
Essa propriedade indica **qual função construiu aquele protótipo**.

Por padrão, ela aponta para a função construtora correta. Mas **quando usamos `Object.create()` para fazer herança**, ela pode acabar apontando para a função errada!

```javascript
function Pessoa(nome) {
  this.nome = nome;
}
function Professor(nome, materia) {
  Pessoa.call(this, nome);
  this.materia = materia;
}

// Herança
Professor.prototype = Object.create(Pessoa.prototype);

console.log(Professor.prototype.constructor); 
// 👉 Resultado: Pessoa ❌

Professor.prototype.constructor = Professor; // Corrigindo ✅

console.log(Professor.prototype.constructor);
// 👉 Resultado: Professor ✅
```

***

#### 🧪 Recapitulando com uma tabela:

| Expressão                         | O que é?                                  | Tipo/Retorno                             |
| --------------------------------- | ----------------------------------------- | ---------------------------------------- |
| `Professor.prototype`             | Protótipo dos objetos `Professor`         | Objeto                                   |
| `Professor.constructor`           | Quem criou a função `Professor`           | `Function`                               |
| `Professor.prototype.constructor` | Quem criou o **protótipo** do `Professor` | `Professor` (ou incorretamente `Pessoa`) |

***

> ⚠️ **Atenção:** Confundir `constructor` com `prototype.constructor` é um dos erros mais comuns entre quem está começando com herança em JS! 😅

***

#### ✅ Dica prática

Sempre que fizer herança com `Object.create()`, **verifique se `prototype.constructor` está apontando corretamente.**\
Se não estiver, corrija manualmente:

```javascript
Filho.prototype.constructor = Filho;
```

***

### 🔍 **Observação: O que retorna quando usamos apenas `Professor.prototype`?**

Quando você acessa `Professor.prototype` diretamente no seu código, está consultando o **objeto protótipo associado à função construtora** `Professor`.

📦 Em outras palavras:

```javascript
console.log(Professor.prototype);
```

> 🟰 Retorna um **objeto que servirá como base para todas as instâncias** criadas com `new Professor()`.

***

#### 🧠 O que tem dentro desse objeto?

Por padrão, ele começa assim:

```javascript
function Professor() {}

console.log(Professor.prototype);
// 👉 Resultado:
{
  constructor: ƒ Professor()
}
```

Ou seja, ele contém:

* A propriedade `constructor`, que aponta de volta para a própria função `Professor`.

***

#### 💡 Pra que serve esse objeto?

É nesse `Professor.prototype` que você pode **adicionar métodos ou propriedades compartilhadas** entre todas as instâncias da função.

```javascript
Professor.prototype.darAula = function () {
  console.log("Dando aula!");
};

const prof1 = new Professor();
const prof2 = new Professor();

prof1.darAula(); // Dando aula!
prof2.darAula(); // Dando aula!
```

✅ Ambos os objetos compartilham o mesmo método **sem duplicar o código na memória**!

***

#### 📌 Por que isso é importante?

Entender o que `Professor.prototype` retorna é essencial para:

* Trabalhar com **herança** corretamente
* Criar **métodos otimizados**
* Entender como funciona o **encadeamento de protótipos**
* Evitar confusão com `__proto__` e `prototype.constructor`

***

#### 🛑 Atenção

> `Professor.prototype` **não é o protótipo da função Professor**.\
> Ele é o protótipo que será usado pelas **instâncias criadas com `new Professor()`**!

🔁 Já o `Professor.__proto__` (com dois underlines) aponta para `Function.prototype`, pois `Professor` é uma função.

***

#### ✅ Resumo rápido

| Expressão                   | Significado                                          |
| --------------------------- | ---------------------------------------------------- |
| `Professor.prototype`       | Objeto usado como protótipo pelas instâncias criadas |
| `new Professor().__proto__` | Sempre é igual a `Professor.prototype`               |

***

### 📘 ECMAScript 2015 (ES6): Herança com classes

A sintaxe moderna com `class` facilita muito o uso da herança:

```javascript
class Pessoa {
  constructor(nome) {
    this.nome = nome;
  }

  falar() {
    console.log(`Olá, meu nome é ${this.nome}`);
  }
}

class Professor extends Pessoa {
  constructor(nome, materia) {
    super(nome); // 👈 Chama o construtor da superclasse
    this.materia = materia;
  }

  falar() {
    console.log(`Sou o prof. ${this.nome} e ensino ${this.materia}`);
  }

  get especialidade() {
    return this.materia.toUpperCase();
  }

  set especialidade(novaMateria) {
    this.materia = novaMateria;
  }
}

const prof2 = new Professor("Joana", "Biologia");
prof2.falar(); // "Sou o prof. Joana e ensino Biologia"
console.log(prof2.especialidade); // BIOLOGIA
prof2.especialidade = "Química";
console.log(prof2.especialidade); // QUÍMICA
```

***

### 🔍 Como funciona por debaixo dos panos?

Mesmo com a sintaxe `class`, **o JavaScript continua usando protótipos!** A classe nada mais é que **um açúcar sintático** para facilitar a vida de quem programa.

```javascript
console.log(Object.getPrototypeOf(Professor)); // [Function: Pessoa]
console.log(Object.getPrototypeOf(prof2)); // Pessoa { ... }
```

***

### 🧩 Onde posso definir propriedades e métodos?

| Local                            | Como declarar                                   | Quando usar                                                   |
| -------------------------------- | ----------------------------------------------- | ------------------------------------------------------------- |
| Dentro da função construtora     | `this.nome = "..."`                             | Quando cada instância precisa de um valor diferente           |
| No próprio construtor (estático) | `MinhaClasse.algumaFuncao = function () {}`     | Quando o método é da classe, não do objeto                    |
| No protótipo                     | `MinhaClasse.prototype.metodo = function () {}` | Quando todas as instâncias compartilham o mesmo comportamento |

***

### 🧭 Quando usar herança em JavaScript?

✔️ Use quando:

* Vários objetos compartilham lógica comum.
* Você quer **reaproveitar métodos** entre objetos semelhantes.
* Precisa organizar melhor **responsabilidades e comportamentos**.

🚫 Evite quando:

* Os objetos têm pouco em comum.
* A herança deixa o código mais confuso do que ajuda.
* **Composição** seria mais clara do que **herança**.

> 💬 **Dica:** Uma boa regra é **"preferir composição à herança"**, mas usar herança quando o comportamento for realmente compartilhado e hierárquico!

***

### ✅ Resumo prático

| Conceito          | Exemplo                                          | Significado                                 |
| ----------------- | ------------------------------------------------ | ------------------------------------------- |
| `call()`          | `Pai.call(this)`                                 | Chama o construtor pai no contexto do filho |
| `Object.create()` | `Filho.prototype = Object.create(Pai.prototype)` | Faz herança prototipada                     |
| `constructor`     | `Filho.prototype.constructor = Filho`            | Corrige a referência do construtor          |
| `extends`         | `class Filho extends Pai {}`                     | Herança com classes (ES6)                   |
| `super()`         | `super()`                                        | Chama o construtor da superclasse           |
| `get/set`         | `get nome() {}`                                  | Criam propriedades especiais                |
| `prototype`       | `Classe.prototype.metodo = ...`                  | Define métodos herdáveis                    |

***

> 📚 Herança é uma ferramenta poderosa, mas deve ser usada com cuidado e propósito. Aprender como o JavaScript lida com isso te transforma num programador ou programadora muito mais preparado(a)! 🚀
