Na programação orientada a objetos, uma classe é um modelo de código de programa extensível para criar objetos, fornecendo valores iniciais para estado (variáveis-membro) e implementações de comportamento (funções ou métodos-membro).
Na prática, muitas vezes precisamos criar muitos objetos do mesmo tipo, como usuários, bens ou qualquer outra coisa.
Como já sabemos no capítulo Construtor, operador "novo", new function
pode ajudar nisso.
Mas no JavaScript moderno, há uma construção de “classe” mais avançada, que introduz novos recursos excelentes que são úteis para programação orientada a objetos.
A sintaxe básica é:
class MinhaClasse { //métodos de classe construtor() { ... } método1() { ... } método2() { ... } método3() { ... } ... }
Em seguida, use new MyClass()
para criar um novo objeto com todos os métodos listados.
O método constructor()
é chamado automaticamente por new
, para que possamos inicializar o objeto lá.
Por exemplo:
classe Usuário { construtor(nome) { este.nome = nome; } digaOi() { alerta(este.nome); } } // Uso: deixe usuário = novo usuário("João"); user.sayHi();
Quando new User("John")
é chamado:
Um novo objeto é criado.
O constructor
é executado com o argumento fornecido e o atribui a this.name
.
…Então podemos chamar métodos de objeto, como user.sayHi()
.
Sem vírgula entre métodos de classe
Uma armadilha comum para desenvolvedores iniciantes é colocar uma vírgula entre os métodos de classe, o que resultaria em um erro de sintaxe.
A notação aqui não deve ser confundida com objetos literais. Dentro da classe, não são necessárias vírgulas.
Então, o que exatamente é uma class
? Essa não é uma entidade inteiramente nova no nível da linguagem, como se poderia pensar.
Vamos desvendar qualquer magia e ver o que realmente é uma aula. Isso ajudará na compreensão de muitos aspectos complexos.
Em JavaScript, uma classe é um tipo de função.
Aqui, dê uma olhada:
classe Usuário { construtor(nome) { this.name = nome; } digaOi() { alerta(este.nome); } } // prova: o usuário é uma função alerta(tipo de usuário); //função
O que a construção class User {...}
realmente faz é:
Cria uma função chamada User
, que se torna o resultado da declaração da classe. O código da função é retirado do método constructor
(assumido como vazio se não escrevermos tal método).
Armazena métodos de classe, como sayHi
, em User.prototype
.
Após a criação new User
, quando chamamos seu método, ele é retirado do protótipo, conforme descrito no capítulo F.prototype. Portanto, o objeto tem acesso aos métodos da classe.
Podemos ilustrar o resultado da declaração class User
como:
Aqui está o código para introspectá-lo:
classe Usuário { construtor(nome) { this.name = nome; } digaOi() { alerta(este.nome); } } //classe é uma função alerta(tipo de usuário); //função // ...ou, mais precisamente, o método construtor alerta(Usuário === Usuário.prototype.construtor); // verdadeiro // Os métodos estão em User.prototype, por exemplo: alerta(User.prototype.sayHi); // o código do método sayHi // existem exatamente dois métodos no protótipo alerta(Object.getOwnPropertyNames(User.prototype)); // construtor, digaOi
Às vezes as pessoas dizem que class
é um “açúcar sintático” (sintaxe projetada para facilitar a leitura, mas que não introduz nada de novo), porque poderíamos na verdade declarar a mesma coisa sem usar a palavra-chave class
:
//reescrevendo a classe User em funções puras // 1. Criar função construtora função Usuário(nome) { este.nome = nome; } // um protótipo de função possui a propriedade "construtor" por padrão, // então não precisamos criá-lo // 2. Adicione o método ao protótipo User.prototype.sayHi = function() { alerta(este.nome); }; // Uso: deixe usuário = novo usuário("João"); user.sayHi();
O resultado desta definição é quase o mesmo. Portanto, existem de fato razões pelas quais class
pode ser considerada um açúcar sintático para definir um construtor junto com seus métodos de protótipo.
Ainda assim, existem diferenças importantes.
Primeiro, uma função criada por class
é rotulada por uma propriedade interna especial [[IsClassConstructor]]: true
. Portanto, não é exatamente o mesmo que criá-lo manualmente.
A linguagem verifica essa propriedade em vários lugares. Por exemplo, diferentemente de uma função regular, ela deve ser chamada com new
:
classe Usuário { construtor() {} } alerta(tipo de usuário); //função Usuário(); // Erro: Construtor de classe O usuário não pode ser invocado sem 'novo'
Além disso, uma representação de string de um construtor de classe na maioria dos mecanismos JavaScript começa com “classe…”
classe Usuário { construtor() {} } alerta(Usuário); //classe Usuário {...}
Existem outras diferenças, as veremos em breve.
Os métodos de classe não são enumeráveis. Uma definição de classe define o sinalizador enumerable
como false
para todos os métodos no "prototype"
.
Isso é bom, porque se for..in
sobre um objeto, geralmente não queremos seus métodos de classe.
As aulas sempre use strict
. Todo o código dentro da construção da classe está automaticamente no modo estrito.
Além disso, a sintaxe class
traz muitos outros recursos que exploraremos mais tarde.
Assim como as funções, as classes podem ser definidas dentro de outra expressão, passadas, retornadas, atribuídas, etc.
Aqui está um exemplo de expressão de classe:
deixe Usuário = classe { digaOi() { alerta("Olá"); } };
Semelhante às Expressões de Função Nomeada, as expressões de classe podem ter um nome.
Se uma expressão de classe tiver um nome, ele será visível apenas dentro da classe:
// "Expressão de classe nomeada" // (não existe esse termo na especificação, mas é semelhante à Expressão de Função Nomeada) deixe Usuário = class MinhaClasse { digaOi() { alerta(MinhaClasse); // O nome da minhaClasse é visível apenas dentro da classe } }; novo usuário().sayHi(); // funciona, mostra a definição de MyClass alerta(MinhaClasse); // erro, o nome da MinhaClasse não é visível fora da classe
Podemos até fazer aulas dinamicamente “sob demanda”, assim:
function makeClass(frase) { //declara uma classe e a retorna classe de retorno { digaOi() { alerta(frase); } }; } //Cria uma nova classe deixe Usuário = makeClass("Olá"); novo usuário().sayHi(); // Olá
Assim como os objetos literais, as classes podem incluir getters/setters, propriedades computadas, etc.
Aqui está um exemplo de user.name
implementado usando get/set
:
classe Usuário { construtor(nome) { // invoca o setter este.nome = nome; } obter nome() { retorne isto._nome; } definir nome(valor) { if (valor.comprimento <4) { alert("O nome é muito curto."); retornar; } this._name = valor; } } deixe usuário = novo usuário("João"); alerta(usuário.nome); // John usuário = novo usuário(""); // O nome é muito curto.
Tecnicamente, essa declaração de classe funciona criando getters e setters em User.prototype
.
Aqui está um exemplo com um nome de método computado usando colchetes [...]
:
classe Usuário { ['diga' + 'Oi']() { alerta("Olá"); } } novo usuário().sayHi();
Tais características são fáceis de lembrar, pois se assemelham às de objetos literais.
Navegadores antigos podem precisar de um polyfill
Os campos de classe são uma adição recente ao idioma.
Anteriormente, nossas classes só tinham métodos.
“Campos de classe” é uma sintaxe que permite adicionar quaisquer propriedades.
Por exemplo, vamos adicionar a propriedade name
à class User
:
classe Usuário { nome = "João"; digaOi() { alerta(`Olá, ${este.nome}!`); } } novo usuário().sayHi(); // Olá, João!
Então, apenas escrevemos “
A diferença importante dos campos de classe é que eles são definidos em objetos individuais, não em User.prototype
:
classe Usuário { nome = "João"; } deixe usuário = novo usuário(); alerta(usuário.nome); // John alerta(Usuário.protótipo.nome); // indefinido
Também podemos atribuir valores usando expressões e chamadas de função mais complexas:
classe Usuário { nome = prompt("Nome, por favor?", "João"); } deixe usuário = novo usuário(); alerta(usuário.nome); // John
Conforme demonstrado no capítulo Funções de vinculação de função em JavaScript têm um this
dinâmico. Depende do contexto da chamada.
Portanto, se um método de objeto for passado e chamado em outro contexto, this
não será mais uma referência ao seu objeto.
Por exemplo, este código mostrará undefined
:
classe Botão { construtor(valor) { este.valor = valor; } clique() { alerta(este.valor); } } deixe botão = new Button("olá"); setTimeout(botão.clique, 1000); // indefinido
O problema se chama “perder this
”.
Existem duas abordagens para corrigi-lo, conforme discutido no capítulo Ligação de funções:
Passe uma função wrapper, como setTimeout(() => button.click(), 1000)
.
Vincule o método ao objeto, por exemplo, no construtor.
Os campos de classe fornecem outra sintaxe bastante elegante:
classe Botão { construtor(valor) { este.valor = valor; } clique = () => { alerta(este.valor); } } deixe botão = new Button("olá"); setTimeout(botão.clique, 1000); // olá
O campo de classe click = () => {...}
é criado por objeto, há uma função separada para cada objeto Button
, com this
dentro dele fazendo referência a esse objeto. Podemos passar button.click
em qualquer lugar, e o valor this
sempre estará correto.
Isso é especialmente útil no ambiente do navegador, para ouvintes de eventos.
A sintaxe básica da classe é semelhante a esta:
class MinhaClasse { suporte = valor; // propriedade construtor(...) { // construtor // ... } método(...) {} // método pegue algo(...) {} // método getter definir algo(...) {} // método setter [Symbol.iterator]() {} // método com nome calculado (símbolo aqui) // ... }
MyClass
é tecnicamente uma função (aquela que fornecemos como constructor
), enquanto métodos, getters e setters são escritos em MyClass.prototype
.
Nos próximos capítulos aprenderemos mais sobre classes, incluindo herança e outros recursos.
importância: 5
A classe Clock
(veja o sandbox) é escrita em estilo funcional. Reescreva-o na sintaxe de “classe”.
PS O relógio bate no console, abra-o para ver.
Abra uma sandbox para a tarefa.
classe Relógio { construtor({modelo}) { este.template = modelo; } renderizar() { deixe data = new Data(); deixe horas = date.getHours(); if (horas <10) horas = '0' + horas; deixe minutos = date.getMinutes(); if (minutos <10) minutos = '0' + minutos; deixe segundos = date.getSeconds(); if (seg <10) segundos = '0' + segundos; deixe saída = this.template .replace('h', horas) .replace('m', minutos) .replace('s', segundos); console.log(saída); } parar() { clearInterval(this.timer); } começar() { this.render(); this.timer = setInterval(() => this.render(), 1000); } } deixe relógio = novo Relógio({template: 'h:m:s'}); relógio.start();
Abra a solução em uma sandbox.