Programação Orientada a Objetos
No mundo da programação, existem um modelo de programação em que usamos classes que possuem características que definem um objeto na vida real.
Onde cada uma dessas classes representam estruturas organizadas que facilitam tanto o desenvolvimento do código quanto a leitura e o entendimento por parte do desenvolvedor.
Até o momento atual, nós criamos nossos códigos em Javascript de maneira procedural.
A programação procedural divide o programa em pequenos programas e se refere a eles como funções.
É a mesma coisa que você criar um programa dividido por arquivos, onde existe um arquivo principal que chama as funções que estão dividas em cada um desses arquivos.
Já a a Programação Orientada a Objetos divide o programa em pequenas partes e se refere a elas como objetos.
É possível criar sistemas sem usar esse modelo de programação orientada a objetos? Sim, isso é totalmente possível!
Eu por exemplo, quando comecei a minha jornada como desenvolvedor lá no ano de 2012, eu cheguei a criar uma rede social bem completa e super complexa, onde me baseie na versão do Facebook daquela época.
Com a minha rede social você conseguia se cadastrar, fazer login, postar seus conteúdos, acessar o feed, adicionar amigos, ver sua lista de amigos, criar grupos, criar eventos e tudo mais!
Naquela época eu ainda estava engatinhando nesse ramo de programação, portanto, não sabia nem o que era programação orientada a objetos!
E sem fazer o uso de classes e arrays, construí uma rede social totalmente funcional e tão completa quanto o facebook de 2012.
Mas se você parasse para dar uma olhada no meu código... coisa de louco 🤯
Os arquivos funcionavam como classes, tinha bastante código duplicado e tudo era muuuito confuso!
A verdade é que a programação orientada a objetos veio para resolver esses problemas além de padronizar a forma como desenvolvemos os sistemas.
Então, se eu quiser... eu posso criar sistemas sem seguir esse modelo?
O problema é que se você for trabalhar com outros desenvolvedores (em outras empresas), programação orientada a objetos é algo essencial que todo mundo usa, então se o seu objetivo é arrumar um emprego... bem... você vai precisar aprender esse modelo.
Se algum dia você precisar de uma biblioteca de código existente em um GitHub da vida, saiba que a maioria dos códigos ali presentes fazem uso da programação orientada a objetos.
Agora caso você for um desenvolvedor freelancer que está acostumado a criar sistemas, sem se preocupar muito se o sistema que você criou será usado ou modificado por outros desenvolvedores... bem, daí você pode seguir sem aderir a este modelo.
A verdade é que é muito mais fácil fazer a manutenção de um sistema que segue o modelo orientado a objetos, do que um sistema que segue o modelo procedural.
O que são classes?
Como o próprio nome já nos diz, uma classe pode ser traduzida como um agrupamento de coisas, segundo o dicionário:
"cada um dos grupos ou divisões de uma série ou conjunto; categoria, seção, ordem."
Nesse caso, ela representa um grupo de comandos agrupados e organizados dentro de uma categoria principal, que é chamada de classe.
Por exemplo, no mundo real, nós temos o Reino Animal que é uma categoria que engloba os:
- Poríferos,
- Cnídarios,
- Platelmitos,
- Moluscos,
- Cordados,
- E muitos outros.
Cada qual contendo suas características únicas.
Fazendo uma alusão entre o mundo real e o mundo da programação, podemos dizer que o Reino Animal está para as classes em Javascript.
Podemos dizer que as classes podem ser associadas a uma caixa de ferramentas, onde dentro desse caixa, nós podemos ter chave de fenda, cerrote, pregos, parafusos, furadeiras e muitos outros.
De modo que quando precisamos de uma furadeira, basta apenas abrir a caixa de ferramentas, e não tentar providenciar uma.
As classes foram introduzidas no ECMAScript 2015, e você pode declará-las da seguinte forma:
class ReinoAnimal{
...
}
Olhando assim, a declaração de uma classe parece ser algo bem simples de ser escrito e realmente é, até porque ele tem a mesma sintaxe das estruturas de blocos que vimos em conteúdos anteriores.
É importante ressaltar que no JS podemos ter quantas classes quisermos dentro de um mesmo programa ou um único arquivo, ok?
Atributos e Métodos de uma Classe
Falando agora em termos mais técnicos, uma classe é um tipo estruturado que pode conter membros, e esses membros são conhecidos como:
- Atributos/Propriedades (o que engloba dados/campos).
- Métodos (o que engloba funções/operações)
Vejamos cada um deles abaixo:
Atributos
Um atributo (ou também chamado de propriedades de uma classe) nada mais é do que um membro de uma classe, isso é fato, e já foi dito anteriormente 😅
A forma mais simples de saber o que é um atributo de uma classe, é entender que toda e qualquer variável declarada dentro de uma classe, é considerada um atributo.
Mas calma, que no momento, não é possível declarar variáveis de maneira padrão como estamos vendo no exemplo abaixo:
class Casa{
const dono = "Micilini";
let endereco = "Rua das Andoninhas";
var seguro = true;
}
//O código acima está totalmente ERRADO, isso ainda não é possível no JS.
Se você desenvolve sistemas em outras linguagens de programação, você já deve saber que é totalmente possível declarar variáveis dentro de uma classe da mesma forma como declaramos fora dela.
Só que infelizmente no mundo do JS, isso funciona de uma forma um pouco diferente.
Aqui no Javascript a gente pode fazer o uso dos métodos constructor(), static, public e private fields para criarmos atributos/propriedades.
Tais assuntos nós veremos mais adiante neste conteúdo.
Métodos
Um método também é considerado um membro de uma classe, e pode ser entendido como uma função (sem a declaração 'function') que é declarada dentro de uma classe.
Nesse caso, quando temos "funções" declaradas dentro de uma classe como essas abaixo:
class ReinoAnimal{
Cordados(){
console.log('Os cordados representam o grupo de animais do filo Chordata. São representados por alguns invertebrados aquáticos e todos os vertebrados: peixes, anfíbios, répteis, aves e mamíferos.');
}
Poríferos(){
console.log('Poríferos (Filo Porifera) são animais que vivem no ambiente aquático e que se destacam pela simplicidade de seu corpo, rico em poros.');
}
}
Nós temos na verdade métodos de uma classe.
Portanto, podemos dizer que uma função solta no código é considerado uma função, já uma função (sem a declaração "function") declarada dentro de uma classe, ela assume um outro nome, transformando-se em um método.
class ReinoAnimal{
Cordados(){//Isso é um método!
console.log('Os cordados representam o grupo de animais do filo Chordata. São representados por alguns invertebrados aquáticos e todos os vertebrados: peixes, anfíbios, répteis, aves e mamíferos.');
}
}
function cordados(){//Isso é uma função!
console.log('Os cordados representam o grupo de animais do filo Chordata. São representados por alguns invertebrados aquáticos e todos os vertebrados: peixes, anfíbios, répteis, aves e mamíferos.');
}
Veremos mais adiante que existem algumas formas de se declarar métodos, desde fazendo o uso da hashtag (#), declarações soltas (como vimos no código acima), ou fazendo o uso dos termos static.
Chamando uma classe
Agora que você aprendeu a declarar uma classe junto com alguns métodos, chegou o momento de entender como fazemos para chamar uma classe e executar um método ali existente.
O processo é simples, primeiro nós declaramos a classe:
class MinhaClasse{
MetodoUm(){
console.log('Os cordados representam o grupo de animais do filo Chordata. São representados por alguns invertebrados aquáticos e todos os vertebrados: peixes, anfíbios, répteis, aves e mamíferos.');
}
}
Em seguida nós precisamos instanciar a classe usando o comando new.
const classe = new MinhaClasse();//Processo para instanciar a classe minhaClasse()
Instanciar é como ligar o motor de um carro (girar a chave). Precisamos instanciar uma classe para que futuramente nós possamos continuar usando seus atributos e métodos durante o código.
Com a classe "ligada" (instanciada) dentro de uma variável, basta apenas usar o ponto (.) para acessar algum método, veja como é fácil:
classe.MetodoUm();//"Os cordados representam o grupo de animais do filo Chordata. São representados por alguns invertebrados aquáticos e todos os vertebrados: peixes, anfíbios, répteis, aves e mamíferos."
Está aqui o código completo para que você possa testar:
class minhaClasse{
MetodoUm(){
console.log('Os cordados representam o grupo de animais do filo Chordata. São representados por alguns invertebrados aquáticos e todos os vertebrados: peixes, anfíbios, répteis, aves e mamíferos.');
}
}
const classe = new MinhaClasse();
classe.MetodoUm();
Classes dentro de variáveis
É totalmente possível atribuir uma classe dentro de uma variável, veja um exemplo:
const minhaClasse = class {
meuMetodo(){
console.log('Meu método foi chamado');
}
};
let classe = new minhaClasse();
classe.meuMetodo();//Meu método foi chamado
Aqui o comportamento é bem parecido com as funções anônimas, onde o nome da variável já é considerado o nome da classe.
Strict Mode
Antes de continuarmos nosso assunto sobre classes, precisamos saber o que é, e como funciona o famoso "strict mode" do Javascript.
Cujo significado quer dizer "modo estrito", ele nada mais é do que uma diretiva que faz com que o JS interprete a linguagem de maneira mais rigorosa.
Você se lembra que o Javascript é um tipo de linguagem dinâmica, e que deixa passar algumas coisas, diferente de outras linguagem em que tal prática daria um erro de compilação na hora?
Então... a verdade é que a linguagem do Javascript não foi planejada para as dimensões em que é usada hoje em dia.
Nesse caso, podemos dizer que a linguagem tem alguns probleminhas 😓
Por exemplo, no quesito de declarações de variáveis, também é possível declarar variáveis sem fazer o uso do var, let ou const:
nome = "Micilini";
console.log(nome);//Micilini
É claro que isso não foi tratado em jornadas anteriores, pois tal prática não é certa, ok?
Quando você declara uma variável sem usar var, let ou const, o javascript entende que você criou uma variável global. E esse tipo de coisa é permitida aqui no mundo do JS.
O problema é que se você começar a escrever variáveis dessa forma, você pode acabar sobrescrevendo um valor que pode estar sendo usado por alguma outra função em algum outro lugar, causando erros que são difíceis de encontrar no código.
Ok, então porque os desenvolvedores da linguagem do JS não corrigem isso?
Bem, o que acontece é o seguinte... no momento atual, principalmente na WEB, o JS está presente em 90% dos sites, e devido a essa grande adoção, ainda existem códigos e bibliotecas que usam desde a primeira versão do JS até a mais atual.
O problema é que se corrigirem isso, muitos códigos irão quebrar e algumas funcionalidades de sites podem parar de funcionar.
Por isso que o JS ainda dá suporte de forma padrão a esses "bugs" do código. É claro que em versões mais atuais, novas formas de se escrever variáveis foram surgindo, mas as antigas ainda permaneceram rs
Quando fazemos o uso do comando "use strict":
"use strict"
nome = "Micilini";
console.log(nome);//Uncaught ReferenceError: nome is not defined"
Nós estamos forçando para que a linguagem faça o uso das boas práticas, de modo que o código acima dê um erro de compilação.
É importante ressaltar que navegadores que reconhecem essa diretiva vão emitir erros quando encontram códigos inválidos no javascript. Já navegadores antigos que não reconhecem esse comando, eles simplesmente ignoram a diretiva e continuam executando os códigos normalmente sem problema algum.
Como o "use strict" pode ser usado em blocos de código, você pode utilizar dentro das suas novas funções sem precisar refatorar (escrever todo o código novamente) todas suas bibliotecas antigas.
numero = 21;//funciona sem problemas porque esta fora do bloco
function verificar() {
"use strict";
idade = 1;//emite erro em navegadores suportados
}
É como se você falasse para o JS assim: "Hey JS, tudo o que estiver dentro desse bloco, deve usar as boas práticas, agora o que existe fora dele, bem... interprete da maneira que quiser..".
Agora que você já entende sobre o Strict Mode, saiba que você poderá encontrar esses comandos em diversas bibliotecas no JS, portanto, não se assuste quando encontrar o "use strict" 🤓
O Corpo de uma Classe
Como vimos anteriormente, o corpo de uma classe é tudo aquilo que se encontra entre as chaves {}:
class MinhaClasse{
//Tudo o que está aqui dentro é o corpo da classe
}
É no corpo da classe que você vai definir os métodos e atributos da mesma.
Saiba que o corpo da classe é executado no modo estrito de forma padrão, mesmo que você não faça o uso do "use strict", ok?
Isso porque, as classes foram implementadas em versões mais novas do JS, então é meio obvio que ela já vem com boas práticas, não é verdade?
Uma classe pode ser caracterizada em 3 aspectos principais:
- Tipo: getter, setter, método ou campo.
- Local: Estático ou instância
- Visibilidade: Pública ou privada
Aprenderemos mais sobre cada um deles a seguir.
Método Construtor (Construct)
Por padrão, o JS conta com um método especial que pode ser declarado em qualquer classe que você for criar, este método tem um nome e é chamado de método construtor.
A sua missão é executar uma sequencia de comandos, assim que uma classe for instanciada, e isso inclui receber argumentos também.
class MinhaClasse{
constructor() {
console.log('A classe foi chamada, e o método construtor vai executar essa mensagem automaticamente');
}
}
const classe = new MinhaClasse();//A partir desse ponto o método construtor é chamado
Infelizmente só pode haver um método especial com o nome "construtor" em uma classe — um SyntaxError é lançado se a classe contiver mais de uma ocorrência de um método construtor —, diferente de outras linguagens onde isso é permitido, ou seja, tem linguagens que você pode definir mais de 1 método construtor.
Apesar disso, o método construtor costuma ser declarado quando queremos passar argumentos para dentro de uma classe, como por exemplo:
class MinhaClasse{
constructor(nome){
console.log('Olá: ' + nome);
}
}
const classe = new MinhaClasse('Micilini');//Olá: Micilini
Lembrando que podemos passar mais de um único argumento para dentro de uma classe, é só separa-los por virgula, seguindo a mesma lógica dos argumentos de uma função.
class MinhaClasse{
constructor(nome, idade){
console.log('Olá: ' + nome + ', Idade: ' + idade);
}
}
const classe = new MinhaClasse('Micilini', 25);//Olá: Micilini, Idade: 25
Definindo atributos a uma classe com o constructor()
Lembra que anteriormente eu disse que é possível declarar atributos/propriedades de uma classe por meio do método constructor()?
Para fazer isso você vai precisar, primeiro declarar uma função com um método construtor que recebe argumentos:
class MinhaClasse{
constructor(nome, idade){
//continue aqui...
}
}
Segundo, dentro do método construtor, você vai precisar fazer referências aos atributos da classe, de modo que por de baixo dos panos o JS vai criar esses atributos caso eles não existam.
E como eles não existem nesse exemplo, o JS vai criá-los. Agora vamos entender como isso funciona na prática.
Para fazer uma referência a um atributo de uma classe, usamos o comando this seguido do ponto (.) com o nome do atributo:
constructor(nome, idade){
this.nome = nome;//Será criado um novo atributo chamado 'nome'
this.idade = idade;//Será criado um novo atributo chamado 'idade'
}
No código acima nós estamos definindo o valor que está armazenado na variável temporária nome para dentro do atributo da classe que também se chama nome.
Mas como não existe nenhum atributo nome previamente declarado dentro da classe, o JS se encarrega de criar um por de baixo dos panos, e a lógica também segue para o atributo idade.
Essa é uma das formas de declarar atributos em classes, para que posteriormente possamos usa-los depois.
É por meio do comando this que podemos selecionar um atributo e mostrar o seu valor, veja o exemplo abaixo:
class MinhaClasse{
constructor(nome, idade){//Aqui estamos salvando os argumentos enviados para seus respectivos atributos que serão criados de maneira automática pelo JS
this.nome = nome;
this.idade = idade;
}
mostraMensagem(){
console.log('Nome: ' + this.nome + ', Idade: ' + this.idade);//Aqui estamos recuperando o atributo da classe que pode ser acessado por qualquer método
}
}
const classe = new MinhaClasse('Micilini', 25);
classe.mostraMensagem();//Nome: Micilini, Idade: 25
Como alternativa, nem sempre nós queremos declarar atributos dessa forma, por vezes queremos escrever os atributos dentro da classe, e isso só é possível com os campos da classe (class fields).
Campos da Classe (Class Fields)
Você sabia que é possível escrever nossos próprios atributos dentro da classe como se eles fossem variáveis globais?
No caso das funções, nós conseguimos fazer isso tranquilamente dessa forma:
function minhaFuncao(){
let nome = 'Micilini';
var idade = 25;
const https = true;
}
Mas será que é possível fazer isso dentro de classes também?
Sim isso é possível por meio do class fields, e é como declarar variáveis sem o uso do var, let ou const, vejamos:
class MinhaClasse{
nome = 'Micilini';
idade = 25;
}
Os campos de classe são semelhantes às propriedades do objeto, e não às variáveis, por esse motivo que não usamos os comandos var, let ou const para declará-los.
Já quando usamos o método construtor quando já tem atributos declarados, o JS não precisa criá-los por de baixo dos panos, pois ele já faz uma referência aos que já existem.
class MinhaClasse{
nome = 'Micilini';
idade = 25;
constructor(nome, idade){
this.nome = nome;//Faz referencia aos atributos acima (não precisa cria-los por de baixo dos panos)
this.idade = idade;//Faz referência aos atributos acima (não precisa cria-los por de baixo dos panos)
}
mostraMensagem(){
console.log('Nome: ' + this.nome + ', Idade: ' + this.idade);//Aqui estamos recuperando o atributo da classe que pode ser acessado por qualquer método
}
}
const classe = new MinhaClasse('João', 19);
classe.mostraMensagem();//Nome: João, Idade: 19, a mensagem saiu diferente porque atualizamos os valores dos atributos
Também é possível declarar atributos sem valor algum:
class MinhaClasse{
nome = 'Micilini';
idade;
}
Atributos Públicos
Você sabia que todos os atributos que declaramos anteriormente são público? E que podem ser acessados e alterados por comandos de fora da classe ou métodos de dentro da classe?
Quando declaramos campos de classe da forma como fizemos até agora, estamos dizendo que esses atributos são todos públicos.
Isso significa que eles podem ser acessados e alterados por métodos de dentro da classe, ou por comandos que existem de fora da classe, vejamos:
class MinhaClasse{
nome = 'Micilini';
idade = 25;
alteraNome(nome){
this.nome = nome;
}
}
const classe = new MinhaClasse();
console.log(classe.nome);//Micilini
classe.idade = 28;
console.log(classe.idade);//28 em vez de 25
classe.alteraNome('João');
console.log(classe.nome);//João
Atributos Privados
Quando você deseja que os atributos de uma classe só sejam alterados ou acessados por métodos de dentro da classe, e não fora, você pode criar atributos privados.
Para criar um atributo privado é simples, basta adicionar a hashtag (#) antes do nome do atributo:
class MinhaClasse{
#nome = 'Micilini';
}
const classe = new MinhaClasse();
console.log(classe.nome);//undefined
Enquanto em outras linguagens, o ato de você tentar acessar atributos privados geraria um erro de compilação, mas como o nosso querido amigo javascript é bem flexível... ele deixa isso passar e retorna um undefined 😂
Por que usar atributos privados?
Além de uma questão de confiança de código, também é adicionado a equação o gerenciamento da complexidade do mesmo.
Um membro público pode ser acessado de fora da classe, o que, para considerações práticas, significa "potencialmente em qualquer lugar". Se algo der errado com um campo público, o culpado pode estar em qualquer lugar e, portanto, para rastrear o bug, você pode ter que examinar bastante código.
Um membro privado, por outro lado, só pode ser acessado de dentro da mesma classe, portanto, se algo der errado com isso, geralmente haverá apenas um arquivo de origem para examinar. Se você tem um milhão de linhas de código em seu projeto, mas suas classes são mantidas pequenas, isso pode reduzir seu esforço de rastreamento de bugs em um fator de 1000.
Quando declaramos atributos públicos, da a ideia de que o desenvolvedor pode usa-los e quem sabe altera-los a seu favor, já quando usamos atributos privados, o desenvolvedor já entende que dentro daqueles atributos estarão armazenadas informações sensíveis que não devem ser alteradas de fora da classe, que se fosse alterados por alguém de fora, poderia acarretar um bug no código.
Por exemplo, vamos imaginar que eu tenha uma classe chamada de gerarToken, onde contém um atributo privado chamado token com um valor pré-definido.
class GerarToken{
#token = 'AHZ7s89annjs';
mostrarToken(nome){
console.log(nome + this.token);
}
}
Vamos considerar que essa classe ela é usada para concatenar o nome do usuário com um token, para que mais tarde um outro sistema que faz o uso desse mesmo token, verifique se o final da string bate com o token especificado.
Essa classe é um exemplo do uso dos atributos privados, porque se o atributo token fosse público, o desenvolvedor poderia muito bem alterar esse token, de modo a gerar uma mensagem errada, que seria negada pelo outro sistema durante o processo de verificação.
É claro que o uso dos atributos privados vão muito além disso, mas acredito que você já tenha entendido o conceito principal.
Métodos Públicos e Privados
Da mesma forma que acontece com os atributos, também podemos declarar métodos públicos e privados.
Com relação aos métodos públicos, nós já sabemos como eles funcionam, é so declarar uma função sem o comando function:
class MinhaClasse{
meuMetodoPublico(){
//Corpo do método público...
}
}
Com isso nos conseguimos acessá-los tanto por outros métodos de dentro da classe (usando o comando this) ou comandos de fora da classe.
class MinhaClasse{
meuMetodoUm(){
this.meuMetodoDois();
}
meuMetodoDois(){
console.log('Dois');
}
}
const classe = new MinhaClasse();
classe.meuMetodoUm();
Já com relação aos métodos privados, você só precisa declarar a hashtag (#) na frente do método, dessa forma:
class MinhaClasse{
#meuMetodoPrivado(){
//Corpo do método privado
}
}
Com relação aos métodos privados, eles só podem ser chamados por outros métodos que existem dentro da classe. Nesse caso precisamos sempre ter um método público para ser chamado de fora da classe para que este possa chamar um método privado.
Para chamar um método privado usamos o comando this seguido do ponto (.), da hashtag (#) e o nome do método privado.
Vejamos como tudo isso funciona abaixo:
class MinhaClasse{
meuMetodoPublico(){
console.log('Método público chamado...');
this.#meuMetodoPrivado();//Chamando um método privado
}
#meuMetodoPrivado(){
console.log('Método privado chamado...');
}
}
const classe = new MinhaClasse();
classe.meuMetodoPublico();//"Método público chamado..." - "Método privado chamado..."
classe.meuMetodoPrivado();//Erro 'Uncaught TypeError: classe.meuMetodoPrivado is not a function"'
A mesma coisa funciona para chamarmos um método público de dentro da classe, a diferença é que não precisamos informar a hashtag (#) antes do nome da classe, uma vez que não se trata de um método privado.
class MinhaClasse{
meuMetodoPublicoUm(){
console.log('um');
this.meuMetodoPublicoDois();
}
meuMetodoPublicoDois(){
console.log('dois');
}
}
const classe = new MinhaClasse();
classe.meuMetodoPublicoUm();
Um método privado pode chamar um outro método público ou privado?
Sim, vejamos como isso acontece:
class MinhaClasse{
meuMetodoPublicoUm(){
console.log('Público 1...');
this.#meuMetodoPrivadoUm();
}
#meuMetodoPrivadoUm(){
console.log('Privado 1...');
this.meuMetodoPublicoDois();
this.#meuMetodoPrivadoDois();
}
meuMetodoPublicoDois(){
console.log('Público 2...');
}
#meuMetodoPrivadoDois(){
console.log('Privado 2...');
}
}
const classe = new MinhaClasse();
classe.meuMetodoPublicoUm();//"Público 1..." - "Privado 1..." - "Público 2..." - "Privado 2..."
Um método público/privado pode ter acesso a um atributo público/privado?
Sim, vejamos como isso acontece:
class MinhaClasse{
atributoPublico = 1;
#atributoPrivado = 2;
meuMetodoPublicoUm(){
console.log(this.atributoPublico + ' - ' + this.#atributoPrivado);
this.#meuMetodoPrivadoUm();
}
#meuMetodoPrivadoUm(){
console.log(this.atributoPublico + ' - ' + this.#atributoPrivado);
}
}
const classe = new MinhaClasse();
classe.meuMetodoPublicoUm();"1 - 2" - "1 - 2"
Lembrando que o método construtor também consegue chamar e acessar método e atributos públicos e privados.
Métodos e Atributos Estáticos (Static)
Antes de entendermos como funciona o uso dos métodos e atributos estáticos, precisamos compreender o que nos faz a usar o método estático.
Vamos considerar em um primeiro momento que temos uma classe chamada de retângulo, e um método chamado de retornarNumeroDeLados:
class Retangulo {
constructor(lado) {
this.lado = lado;
}
retornarNumeroDeLados() {
return this.side;
}
}
Agora vamos criar duas instancias da classe retangulo, enviando argumentos diferentes:
let retangulo1 = new Retangulo(4);
let retangulo2 = new Retangulo(8);
Se quisermos retornar o lado da primeira instancia do lado do retangulo, poderíamos fazer isso usando o método retornarNumeroDeLados().
Quando chamamos esse método, é utilizada o comando this, que por definição refere-se ao atributo por meio do qual o método está invocando. Nesse caso, é valido dizer que quando o método é invocado (retornarNumeroDeLados()), automaticamente o método é vinculado ao atributo.
Por vezes teremos situações em que desejamos invocar um método sem a necessidade de vincula-lo a um atributo, e isso só é possível por meio dos métodos estáticos.
O que é um método estático?
Um método estático é um método definido em uma classe usando o comando "static" antes do nome do método:
class MinhaClasse {
static mostrarMensagem() {
console.log('Olá mundooo!');
}
}
Ele atua de forma diferente de um método comum, pois ele pode ser acessado usando somente o nome da classe sem a necessidade de instanciar essa classe:
class MinhaClasse {
static mostrarMensagem() {
console.log('Olá mundooo!');
}
}
MinhaClasse.mostrarMensagem();//Olá mundooo!
//Observe que não precisamos do comando 'new'
Caso preferir você também pode usar o método para retornar algum valor:
class MinhaClasse {
static mostrarMensagem() {
return 'Olá mundooo!';//Precisamos de um retorno no método estático
}
}
console.log(MinhaClasse.mostrarMensagem());//Olá mundooo!
Podemos acessar um método estático instanciando uma classe?
Não, pois métodos estáticos não podem ser chamados por meio de objetos (classes instanciadas)
class MinhaClasse{
static retornaMensagem(){
return 'Olá mundooo!';
}
}
const classe = new MinhaClasse();
console.log(classe.retornaMensagem());//Erro 'Uncaught TypeError: classe.retornaMensagem is not a function'
Podemos acessar um método estático por meio de um método não estático dentro da classe?
Isso não é possível usando o this, o que podemos fazer é chamar o nome da classe junto com o método estático.
class MinhaClasse{
mostrarMensagem(){
console.log(MinhaClasse.retornaMensagem());
}
static retornaMensagem(){
return 'Olá mundooo!';
}
}
const classe = new MinhaClasse();
classe.mostrarMensagem();//Olá mundooo!
Podemos acessar um método estático por meio do construtor da classe?
Sim, podemos fazer isso da mesma forma que no exemplo anterior, chamando o nome da classe junto com o método estático. O problema é que se você instanciar essa classe mais tarde, o valor enviado para o método estático irá sumir.
class MinhaClasse{
constructor(){
console.log(MinhaClasse.retornaMensagem());
}
static retornaMensagem(){
return 'Olá mundooo!';
}
}
const classe = new MinhaClasse();//Olá Mundooo!
Podemos acessar um método estático por meio de outro método estático?
Sim, isso é totalmente possível e nesse caso o comando this se faz necessário:
class MinhaClasse{
static retornaNome(){
return 'Nome: ' + this.pegaNome();
}
static pegaNome(){
return 'Micilini';
}
}
console.log(MinhaClasse.retornaNome());//Nome; Micilini
Podemos acessar um método não estático por meio de um método estático?
Não, isso não é possível, pois métodos estáticos só podem ser chamados por métodos estáticos, e métodos não estáticos só podem ser chamados com métodos não estáticos.
class MinhaClasse{
static retornaNome(){
return 'Nome: ' + this.pegaNome();
}
pegaNome(){
return 'Micilini';
}
}
console.log(MinhaClasse.retornaNome());//Erro 'Uncaught TypeError: this.pegaNome is not a function'
Alocação de Memoria de Métodos de uma classe
É importante que você entenda, que todo e qualquer comando que você cria em JS, vai consumir memória da máquina, que pode ser a memoria do seu computador, do notebook, do seu aparelho celular ou quem sabe do seu servidor.
Alguns comandos consomem mais memória do que outros, e com os métodos de uma classe, isso não é diferente.
Quando criamos uma classe e instanciamos ela (new MinhaClasse), estamos alocando na memória da máquina uma referência para aquela classe, de modo que podemos acessá-la novamente mais tarde por meio da variável que guarda essa instancia.
Obviamente que o JS tira essa instancia da memória quando o código termina de ser executado.
Já quando temos métodos estáticos, não precisamos instanciar aquela classe, sendo assim, o método estático é alocado na memória apenas uma vez.
Ou seja, ele aloca a classe na memória, faz o que tem que fazer e retira da memória quando o bloco do método estático é executado por completo.
Isso também implica que o método estático não é compartilhado entre os objetos instanciados porque tal método não pode ser invocado por nenhum desses objetos instanciados.
Por que precisamos de métodos estáticos?
A ideia de ter um método estático em JavaScript surge quando queremos associar o método a uma classe ao invés de qualquer objeto instanciado.
Geralmente usamos os métodos estáticos quando queremos criar operações utilitárias, ou seja, uma classe cujo objetivo é trabalhar ou converter dados por exemplo, onde só precisamos chamar a classe e um determinado método.
Pense novamente no exemplo da classe sendo uma caixa de ferramentas.
Ou quem sabe quando precisamos criar métodos que retornam informações padrão de uma classe.
Diferenças entre Métodos Estáticos e Métodos não Estáticos
Existem grandes diferenças entre esses dois métodos, tais diferenças que já foram discutidas anteriormente neste conteúdo, mas para que você possa entender de forma rápida, separamos alguns pontos:
- Um método pode se tornar estático apenas acrescentando a ele o comando static.
- Um método estático é um método de uma classe, que não representa um objeto instanciado.
- Um método estático não pode ser acessado por objetos instanciados.
- Um método estático pode ser acessado usando o nome da classe sem a necessidade de instanciar esta classe.
- Em termos técnicos, o método estático é armazenado dentro do construtor da classe, mas não no protótipo da classe.
- Métodos estáticos não são compartilhados com objetos instanciados.
- Sempre que um método estático é definido, sua memória é alocada apenas uma vez.
- Métodos estáticos geralmente são usados para criar um objeto contendo informações padrão ou para criar funções utilitárias.
- A herança também funciona para métodos estáticos.
Métodos Estáticos | Métodos não Estáticos |
Pertence diretamente à classe. | Pertence à classe indiretamente. |
Não pode ser invocado por meio de objetos instanciados. | Pode ser invocado por meio de objetos instanciados. |
O comando this, quando usado dentro do método, refere-se à classe. | O comando this quando usada dentro do método, refere-se ao objeto através do qual o método é invocado. |
O que são atributos estáticos?
Assim como os métodos estáticos, existem também os atributos estáticos, que podem ser declarados desta maneira:
class Artigo {
static autor = "LKin Huyter";
}
Como o atributo é estático, ele pertence a classe, e pode ser acessado por membros estáticos de dentro da classe ou diretamente por fora da classe:
class Artigo {
static autor = "LKin Huyter";
static retornaAutor(){
return 'Autor: ' + this.autor;
}
}
console.log(Artigo.autor);//"LKin Huyter"
//Observe que não precisamos instanciar a classe para acessar o atributo
console.log(Artigo.retornaAutor());//"Autor: LKin Huyter"
Será que podemos acessar os atributos estáticos por métodos não estáticos?
Não, pois o JS retornará undefined:
class Artigo {
static autor = "LKin Huyter";
static retornaAutor(){
return 'Autor: ' + this.autor;
}
retornaAutorDois(){
return 'Autor 2: ' + this.autor;
}
}
console.log(Artigo.autor);//"LKin Huyter"
console.log(Artigo.retornaAutor());//"Autor: LKin Huyter"
const artigoClass = new Artigo();
console.log(artigoClass.retornaAutorDois());//"Autor 2: undefined"
Lembrando que os atributos estáticos podem ser alterados por métodos estáticos de dentro da classe, como também de fora da classe:
class Artigo {
static autor = "LKin Huyter";
static modificaAutor(autor){
this.autor = autor;
}
}
console.log(Artigo.autor);//LKin Huyter
Artigo.autor = "Micilini";
console.log(Artigo.autor);//Micilini
Artigo.modificaAutor("Micilini Roll");
console.log(Artigo.autor);//Micilini Roll
Atributos e Métodos Estáticos (Públicos & Privados)
A mesma lógica de definição de métodos e atributos públicos ou privados também se aplica ao static, vejamos:
class MinhaClasse{
//Atributos públicos/privados não estáticos
atributoPublico = true;
#atributoPrivado = false;
//Atributos públicos/privados estáticos
static atributoPublicoEstatico = true;
static #atributoPrivadoEstatico = false;
//Métodos públicos/privados não estáticos
meuMetodoPublico(){
}
#meuMetodoPrivado(){
}
//Métodos públicos/privados estáticos
static meuMetodoPublicoEstatico(){
}
static meuMetodoPrivadoEstatico(){
}
}
Não só a sintaxe como todas as regras que vimos anteriormente neste conteúdo, ok?
Getter
Existem um método no mundo das classes do JS, cuja função principal é somente retornar os valores que estão armazenados dentro dos atributos, conhecidos como métodos do tipo get.
Mas daí você pode se perguntar: "Mas não é só criar um método que faça esse tipo de retono, tipo assim:"
class MinhaClasse{
meuAtributo = 1234;
pegarMeuAtributo(){
return this.meuAtributo;
}
}
const classe = new MinhaClasse();
console.log(classe.pegarMeuAtributo());//1234
Sim, e a ideia principal do método get é justamente essa, a diferença é que ele conta com algumas particularidades:
- Usamos o comando get antes do nome do método.
- Não se envia parâmetros para dentro dos métodos do tipo get.
- Não precisamos usar os parêntesis quando formos chamar estes métodos de fora da classe (mesmo eles sendo do tipo static).
- O nome do método get não deve levar o mesmo nome do atributo, pois se não o JS irá selecionar o atributo diretamente.
- Não pode ser utilizado mais de um getter para um mesmo atributo, assim como não pode haver um atributo comum com o mesmo nome do getter.
Vejamos como os métodos get são definidos:
class MinhaClasse{
meuAtributo = 1234;
get atributo(){//Método do tipo get cuja função é retornar um determinado atributo
return this.meuAtributo;
}
}
const classe = new MinhaClasse();
console.log(classe.atributo);//Observe que não estamos usando os parêntesis ()
A ideia principal dos métodos get é separar métodos cujo objetivo é retornar um atributo, e métodos cujo objetivo é fazer outras coisas além de retornar algum atributo.
Por meio do método get, podemos fazer com que ele retorne um valor dinâmico, ou seja, um valor que ainda passa por um certo tipo de processamento, vejamos:
class Pessoa{
constructor(nome, idade, sexo){
this.nome = nome;
this.idade = idade;
this.sexo = sexo;
}
get dadosDaPessoa(){
return 'Nome: ' + this.nome + ', Idade: ' + this.idade + ', Sexo: ' + this.sexo;
}
}
const pessoa = new Pessoa('Micilini', 25, 'M');
console.log(pessoa.dadosDaPessoa);
Podemos utilizar o método get para retornar um atributo privado?
Sim, além disso, essa é uma das formas mais conhecidas de se retornar atributos privados:
class MinhaClasse{
#meuAtributo = 1234;
get atributo(){//Método do tipo get cuja função é retornar um determinado atributo
return this.#meuAtributo;
}
}
const classe = new MinhaClasse();
console.log(classe.atributo);//Observe que não estamos usando os parêntesis ()
Podemos utilizar o método get em conjunto com métodos estáticos?
Sim, vejamos como isso acontece:
class MinhaClasse{
static meuAtributo = 1234;
static get atributo(){
return this.meuAtributo;
}
}
console.log(MinhaClasse.atributo);
Setters
Assim como os métodos do tipo getter, nós temos os métodos setters cuja principal função é atribuir valores a nossos atributos.
Nesse caso, podemos podemos chamar um método setter passando um parâmetro de modo a alterar o valor de uma determinada propriedade/atributo, vejamos como isso acontece:
class MinhaClasse{
meuAtributo = 1234;
set atributo(valor){
this.meuAtributo = valor;
}
}
let classe = new MinhaClasse();
classe.atributo = 'Lucas';
console.log(classe.meuAtributo) //Lucas
No comando acima, se usássemos o comando classe.meuAtributo = 'Lucas'; estaríamos atribuindo o valor diretamente para o atributo.
Por esse motivo que o método set não pode ser do mesmo nome que o atributo.
Claro que, usando o método setter, podemos fazer algumas alterações nesse atributo antes de salva-lo de forma definitiva.
Sobre o método set, nos podemos dizer que:
- Usamos o comando set antes do nome do método.
- Os parâmetros são enviados por meio do operador de atribuição (=).
- Não precisamos usar os parêntesis quando formos chamar estes métodos de fora da classe.
- O nome do método set não deve levar o mesmo nome do atributo, pois se não o JS irá selecionar o atributo diretamente.
- Não pode ser utilizado mais de um setter para um mesmo atributo, assim como não pode haver um atributo comum com o mesmo nome do setter.
Heranças de Classes
Como o próprio nome já nos diz, uma herança é o montante de bens e valores deixados por um indivíduo para outro.
E no mundo das classes isso não é diferente...
Imagine uma classe PAI que possui alguns atributos e métodos definidos, e uma classe filho que possui seus próprios atributos e métodos, maaaas que herda os atributos e métodos da classe pai.
Herda de uma maneira que o filho consegue usar os atributos e métodos que foram declarados na classe pai, incrível não?
Pense na retrocompatibilidade do Playstation 2 que além de rodar jogos do playstation 2, consegue também rodar jogos do Playstation 1.
Ou quem sabe você pode pensar também naqueles drivers antigos de leitor/gravador de DVD numa época em que a mídia DVD era novidade, lembra que a maioria deles não só conseguia ler e gravar DVD como CD tambem?
Herança em classes no JS é isso!
Para fazer a herança funcionar, primeiro você precisa declarar a classe pai juntamente com seus atributos e métodos:
class Pai{
economias = "R$ 200.000";
temCasaPropria = true;
temCarroProprio = false;
#eCasado = true;
#idade = 40;
falarSobrenome(){
console.log('Sou da familia Roll!');
}
#irTrabalhar(){
console.log('Já estou indo...');
}
}
Em seguida precisamos declarar a classe filho junto com o comando extends, que diz ao JS que essa classe na verdade é uma extensão da classe pai, com isso o filho conseguirá herdar todos seus atributos e métodos:
class Filho extends Pai{
}
Com isso basta apenas instanciar a classe filho que você verá que foi herdado tanto os atributos quanto os métodos:
const filhote = new Filho();
filhote.falarSobrenome();//Sou da familia Roll!
console.log(filhote.economias);//R$ 200.000
Na classe Pai, você percebeu que existem dois atributos privados e um método privado? Será que conseguimos acessar?
console.log(filhote.idade);//undefined
console.log(filhote.eCasado);//undefined
filhote.irTrabalhar();//Erro 'is not a function...'
Não! Pois métodos e atributos privados não são herdados pela classe filho!
Mas o que será que acontece quando escrevemos um atributo ou método que já foi declarado e herdado pela classe pai?
class Filho extends Pai{
temCasaPropria = false;
falarSobrenome(){
console.log('Sou da familia Roll Lins!');
}
}
const filhote = new Filho();
filhote.falarSobrenome();
console.log(filhote.temCasaPropria);
Tanto os atributos, quanto os métodos são sobrescritos! E isso vale também para o método construtor!
Obviamente que a classe filho pode conter seus próprios métodos, observe:
class Filho extends Pai{
estudante = true;
irParaAEscola(){
console.log('Já estou indo estudar!');
}
}
const filhote = new Filho();
filhote.irParaAEscola();
console.log(filhote.estudante);
Os métodos e atributos herdados da classe pai, podem ser utilizados por outros métodos da classe filho? Sim! vejamos:
class Filho extends Pai{
estudante = true;
irParaAEscola(){
console.log('Já estou indo estudar!');
if(this.temCarroProprio){ console.log('Meu pai tem carro :D '); }else{ console.log('Meu pai não tem carro gente :( '); }
console.log('Olha o que meu pai disse hoje de manha: ');
this.falarSobrenome();
}
}
const filhote = new Filho();
filhote.irParaAEscola();
Note que no caso do método irParaAEscola, nós estamos usando um atributo herdado na classe Pai de modo a executar um método daquela classe.
Super
Existe um comando que pode ser usado dentro dos métodos de uma classe filho, na qual consegue acessar métodos e até atributos pertencentes a classe pai.
Usamos o super para chamar os métodos correspondentes da superclasse.
class Pai{
metodoUm(){
console.log('Método Um da classe Pai');
}
}
class Filho extends Pai{
metodoUm(){
console.log('Método Um da classe Filho');
super.metodoUm();
}
}
const filhote = new Filho();
filhote.metodoUm();//"Método Um da classe Filho" - "Método Um da classe Pai"
Observe que mesmo o método sendo reescrito na classe filho, com o comando super ele acessa o método que foi criado na classe pai.
Obviamente que o comando super não é usado somente quando temos métodos sobrescritos, eles também funcionam como o método this.
class Pai{
metodoUm(){
console.log('Método Um da classe Pai');
}
}
class Filho extends Pai{
metodoDois(){
super.metodoUm();//ou this.metodoUm()
}
}
const filhote = new Filho();
filhote.metodoUm();//"Método Um da classe Pai"
Classes não são Hoisted
É importante ressaltar que diferente das variáveis do tipo var, que independentemente de onde elas forem declaradas o JS se encarrega de torna-las global.
As classes não são hosited, isso significa que se você chamar uma classe antes dela ser declarada, o JS não vai reconhecer aquela classe:
const classe = new MinhaClasse();//Erro 'Cannot access 'MinhaClasse' before initialization"'
class MinhaClasse{
}
Portanto antes de instanciar uma classe, certifique-se de que ela foi declarada anteriormente!
class MinhaClasse{
}
const classe = new MinhaClasse();//Agora vai funcionar
Conclusão
Nessa jornada, você mergulhou de cabeça no mundo da programação orientada a objetos, aprendendo tudo e mais um pouco sobre classes e suas particularidades.
Até a próxima jornada 😁