JavaScript usa o modelo de herança prototípica: cada objeto herda campos (propriedades) e métodos do objeto protótipo.
As classes usadas em Java ou Swift como modelos ou esquemas para criar objetos não existem em JavaScript. Existem apenas objetos na herança prototípica.
A herança prototípica pode imitar o modelo clássico de herança de classe. Para fazer isso, o ES6 introduziu a palavra-chave de classe: açúcar sintático para herança prototípica.
Neste artigo, aprenderemos como trabalhar com classes: definir classes, seus campos e métodos privados (privados) e públicos (públicos) e criar instâncias.
1. Definição: a palavra-chave da classe
A palavra-chave class é usada para definir uma classe:
class User {
//
}
Essa sintaxe é chamada de declaração de classe.
A classe pode não ter um nome. Usando uma expressão de classe, você pode atribuir uma classe a uma variável:
const UserClass = class {
//
}
As aulas podem ser exportadas como módulos. Aqui está um exemplo de exportação padrão:
export default class User {
//
}
E aqui está um exemplo de exportação nomeada:
export class User {
//
}
As classes são usadas para criar instâncias. Uma instância é um objeto que contém os dados e a lógica de uma classe.
As instâncias são criadas usando o operador new: instance = new Class ().
Veja como criar uma instância da classe User:
const myUser = new User()
2. Inicialização: construtor ()
construtor (param1, param2, ...) é um método especial dentro de uma classe que inicializa uma instância. É aqui que os valores iniciais dos campos da instância são definidos e configurados.
No exemplo a seguir, o construtor define o valor inicial para o campo de nome:
class User {
constructor(name) {
this.name = name
}
}
O construtor recebe um parâmetro, nome, que é usado para definir o valor inicial do campo this.name.
isso no construtor aponta para a instância que está sendo criada.
O argumento usado para instanciar a classe se torna um parâmetro para seu construtor:
class User {
constructor(name) {
name //
this.name = name
}
}
const user = new User('')
O parâmetro de nome dentro do construtor tem o valor 'Pechorin'.
Se você não definir seu próprio construtor, um construtor padrão é criado, que é uma função vazia que não afeta a instância.
3. Campos
Os campos de classe são variáveis que contêm informações específicas. Os campos podem ser divididos em dois grupos:
- Campos de instância de classe
- Campos da própria classe (estáticos)
Os campos também têm dois níveis de acesso:
- Público (público): os campos estão disponíveis tanto dentro da classe quanto nas instâncias
- Privado (privado): os campos só são acessíveis dentro da classe
3.1. Campos públicos de instâncias de classe
class User {
constructor(name) {
this.name = name
}
}
A expressão this.name = name cria um nome de campo de instância e atribui um valor inicial a ele.
Este campo pode ser acessado usando um acessador de propriedade:
const user = new User('')
user.name //
Nesse caso, nome é um campo público porque pode ser acessado fora da classe Usuário.
Ao criar campos implicitamente dentro de um construtor, é difícil obter uma lista de todos os campos. Para isso, os campos devem ser recuperados do construtor.
A melhor maneira é definir explicitamente os campos da classe. Não importa o que o construtor faça, a instância sempre tem o mesmo conjunto de campos.
A proposta de criação de campos de classe permite definir campos dentro de uma classe. Além disso, aqui você pode atribuir valores iniciais aos campos:
class SomeClass {
field1
field2 = ' '
// ...
}
Vamos alterar o código da classe User, definindo um campo de nome público nela:
class User {
name
constructor(name) {
this.name = name
}
}
const user = new User('')
user.name //
Esses campos públicos são muito descritivos, uma rápida olhada na classe permite que você entenda a estrutura de seus dados.
Além disso, um campo de classe pode ser inicializado no momento da definição:
class User {
name = ''
constructor() {
//
}
}
const user = new User()
user.name //
Não há restrições ao acesso aos campos abertos e sua alteração. Você pode ler e atribuir valores a tais campos no construtor, métodos e fora da classe.
3.2. Campos privados de instâncias de classe
O encapsulamento permite ocultar os detalhes de implementação interna de uma classe. Quem quer que use a classe encapsulada depende da interface pública sem entrar nos detalhes da implementação da classe.
Essas classes são mais fáceis de atualizar quando os detalhes de implementação mudam.
Uma boa maneira de ocultar detalhes é usar campos privados. Esses campos só podem ser lidos e alterados dentro da classe à qual pertencem. Os campos privados não estão disponíveis fora da classe.
Para tornar um campo privado, coloque um símbolo # antes de seu nome, por exemplo, #myPrivateField. Ao fazer referência a tal campo, o prefixo especificado deve sempre ser usado.
Vamos tornar o campo de nome privado:
class User {
#name
constructor(name) {
this.#name = name
}
getName() {
return this.#name
}
}
const user = new User('')
user.getName() //
user.#name // SyntaxError
#name é um campo privado. Ele só pode ser acessado dentro da classe User. O método getName () faz isso.
No entanto, tentar acessar #name fora da classe User irá gerar um erro de sintaxe: SyntaxError: O campo privado '#name' deve ser declarado em uma classe delimitadora.
3.3. Campos estáticos públicos
Em uma classe, você pode definir campos que pertencem à própria classe: campos estáticos. Esses campos são usados para criar constantes que armazenam as informações de que a classe precisa.
Para criar campos estáticos, use a palavra-chave static antes do nome do campo: static myStaticField.
Vamos adicionar um novo campo de tipo para definir o tipo de usuário: administrador ou regular. Os campos estáticos TYPE_ADMIN e TYPE_REGULAR são constantes para cada tipo de usuário:
class User {
static TYPE_ADMIN = 'admin'
static TYPE_REGULAR = 'regular'
name
type
constructor(name, type) {
this.name = name
this.type = type
}
}
const admin = new User(' ', User.TYPE_ADMIN)
admin.type === User.TYPE_ADMIN // true
Para acessar os campos estáticos, use o nome da classe e o nome da propriedade: User.TYPE_ADMIN e User.TYPE_REGULAR.
3.4. Campos estáticos privados
Às vezes, os campos estáticos também fazem parte da implementação interna da classe. Para encapsular esses campos, você pode torná-los privados.
Para fazer isso, prefixe o nome do campo com #: static #myPrivateStaticFiled.
Digamos que desejamos limitar o número de instâncias da classe User. Campos estáticos privados podem ser criados para ocultar informações sobre o número de instâncias:
class User {
static #MAX_INSTANCES = 2
static #instances = 0
}
name
constructor(name) {
User.#instances++
if (User.#instances > User.#MAX_INSTANCES) {
throw new Error(' User')
}
this.name = name
}
new User('')
new User('')
new User('') // User
O campo estático User. # MAX_INSTANCES define o número permitido de instâncias e User. # Instances define o número de instâncias criadas.
Esses campos estáticos privados estão disponíveis apenas dentro da classe User. Nada do mundo externo pode influenciar as restrições: esta é uma das vantagens do encapsulamento.
Aproximadamente. via: se você limitar o número de instâncias a um, obterá uma implementação interessante do padrão de design Singleton.
4. Métodos
Os campos contêm dados. A capacidade de alterar dados é fornecida por funções especiais que fazem parte da classe: métodos.
JavaScript suporta métodos de instância e métodos estáticos.
4.1. Métodos de instância
Os métodos de uma instância de uma classe podem modificar seus dados. Os métodos de instância podem chamar outros métodos de instância, bem como métodos estáticos.
Por exemplo, vamos definir um método getName () que retorna o nome do usuário:
class User {
name = ''
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
const user = new User('')
user.getName() //
Em um método de classe, assim como em um construtor, isso aponta para a instância que está sendo criada. Use para obter dados de instância: this.field, ou para chamar métodos: this.method ().
Vamos adicionar um novo método nameContains (str) que recebe um argumento e chama outro método:
class User {
name
constructor(name) {
this.name = name
}
getName() {
return this.name
}
nameContains(str) {
return this.getName().includes(str)
}
}
const user = new User('')
user.nameContains('') // true
user.nameContains('') // false
nameContains (str) é um método da classe User que recebe um argumento. Ele chama outro método de instância getName () para obter o nome de usuário.
O método também pode ser privado. Para tornar um método privado, use o prefixo #.
Vamos tornar o método getName () privado:
class User {
#name
constructor(name) {
this.#name = name
}
#getName() {
return this.#name
}
nameContains(str) {
return this.#getName().includes(str)
}
}
const user = new User('')
user.nameContains('') // true
user.nameContains('') // false
user.#getName // SyntaxError
#getName () é um método privado. Dentro do método nameContains (str), nós o chamamos assim. # GetName ().
Sendo privado, o método #getName () não pode ser chamado fora da classe User.
4.2. Getters e Setters
Getters e setters são acessores ou propriedades computadas. Esses são métodos que imitam campos, mas permitem que você leia e grave dados.
Getters são usados para receber dados, setters são usados para modificá-los.
Para evitar a atribuição de uma string vazia ao campo de nome, envolva o campo privado #nameValue em um getter e setter:
class User {
#nameValue
constructor(name) {
this.name = name
}
get name() {
return this.#nameValue
}
set name(name) {
if (name === '') {
throw new Error(' ')
}
this.#nameValue = name
}
}
const user = new User('')
user.name // ,
user.name = '' //
user.name = '' //
4.3. Métodos estáticos
Os métodos estáticos são funções que pertencem à própria classe. Eles definem a lógica da classe, não suas instâncias.
Para criar um método estático, use a palavra-chave static na frente do nome do método: static myStaticMethod ().
Ao trabalhar com métodos estáticos, existem duas regras simples para se manter em mente:
- Um método estático tem acesso a campos estáticos
- Não tem acesso aos campos de instância
Vamos criar um método estático para verificar se um usuário com o nome especificado já foi criado:
class User {
static #takenNames = []
static isNameTaken(name) {
return User.#takenNames.includes(name)
}
name = ''
constructor(name) {
this.name = name
User.#takenNames.push(name)
}
}
const user = new User('')
User.isNameTaken('') // true
User.isNameTaken('') // false
isNameTaken () é um método estático que usa o campo privado estático User. # takenNames para determinar quais nomes foram usados.
Os métodos estáticos também podem ser privados: static #myPrivateStaticMethod (). Esses métodos só podem ser chamados dentro da classe.
5. Herança: estende
As classes em JavaScript oferecem suporte à herança usando a palavra-chave extends.
Na expressão, a classe Child estende Parent {}, a classe Child herda construtores, campos e métodos de Parent.
Vamos criar uma classe filha ContentWriter que estende a classe pai User:
class User {
name
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class ContentWriter extends User {
posts = []
}
const writer = new ContentWriter('')
writer.name //
writer.getName() //
writer.posts // []
ContentWriter herda de User o construtor, o método getName () e o campo de nome. O próprio ContentWriter define um novo campo de postagens.
Observe que os campos e métodos privados da classe pai não são herdados pelas classes filho.
5.1. Construtor pai: super () no construtor ()
Para chamar o construtor da classe pai na classe filha, use a função especial super () disponível no construtor da classe filha.
Deixe o construtor ContentWriter chamar o construtor pai e inicializar o campo de postagens:
class User {
name
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class ContentWriter extends User {
posts = []
constructor(name, posts) {
super(name)
this.posts = posts
}
}
const writer = new ContentWriter('', [' '])
writer.name //
writer.posts // [' ']
super (nome) na classe filha ContentWriter chama o construtor da classe pai User.
Observe que super () é chamado no construtor filho antes de usar a palavra-chave this. A chamada super () "liga" o construtor pai à instância.
class Child extends Parent {
constructor(value1, value2) {
// !
this.prop2 = value2
super(value1)
}
}
5,2 Instância pai: super em métodos
Para acessar o método pai dentro da classe filha, use o super atalho especial:
class User {
name
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class ContentWriter extends User {
posts = []
constructor(name, posts) {
super(name)
this.posts = posts
}
getName() {
const name = super.getName()
if (name === '') {
return ''
}
return name
}
}
const writer = new ContentWriter('', [' '])
writer.getName() //
getName () da classe filho ContentWriter chama o método getName () da classe pai User.
Isso é chamado de substituição de método.
Observe que super pode ser usado para métodos estáticos da classe pai também.
6. Verificação do tipo de objeto: instanceof
A instância do objeto da expressão de classe determina se um objeto é uma instância da classe especificada.
Vamos considerar um exemplo:
class User {
name
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
const user = new User('')
const obj = {}
user instanceof User // true
obj instanceof User // false
O operador instanceof é polimórfico: ele examina toda a cadeia de classes.
class User {
name
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class ContentWriter extends User {
posts = []
constructor(name, posts) {
super(name)
this.posts = posts
}
}
const writer = new ContentWriter('', [' '])
writer instanceof ContentWriter // true
writer instanceof User // true
E se precisarmos definir uma classe de instância específica? A propriedade constructor pode ser usada para:
writer.constructor === ContentWriter // true
writer.constructor === User // false
//
writer.__proto__ === ContentWriter.prototype // true
writer.__proto__ === User.prototype // false
7. Classes e protótipos
Deve-se dizer que a sintaxe de classe é uma boa abstração sobre a herança prototípica. Você não precisa fazer referência a protótipos para usar classes.
No entanto, as classes são apenas uma superestrutura na herança prototípica. Qualquer classe é uma função que cria uma instância quando um construtor é chamado.
Os próximos dois exemplos são idênticos.
Aulas:
class User {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
const user = new User('')
user.getName() //
user instanceof User // true
Protótipos:
function User(name) {
this.name = name
}
User.prototype.getName = function () {
return this.name
}
const user = new User('')
user.getName() //
user instanceof User // true
Portanto, compreender as classes requer um bom conhecimento da herança prototípica.
8. Disponibilidade de recursos de classe
Os recursos de classe apresentados neste artigo são divididos entre a especificação ES6 e as propostas no terceiro estágio de consideração:
- Campos de instância públicos e privados
- Métodos e acessores de instância privada
- Campos estáticos públicos e privados e métodos estáticos privados
- Especificação ES6
Aproximadamente. Por: de acordo com Posso usar, o suporte para campos de aulas particulares é atualmente de 68%.
9. Conclusão
Classes em JavaScript são usadas para inicializar instâncias com um construtor, definir seus campos e métodos. A palavra-chave static pode ser usada para definir os campos e métodos da própria classe.
A herança é implementada usando a palavra-chave extends. A palavra-chave super permite que você acesse a classe pai a partir da criança.
A fim de tirar vantagem do encapsulamento, ou seja, ocultar detalhes de implementação interna, tornar campos e métodos privados. Os nomes de tais campos e métodos devem começar com um símbolo #.
As aulas são onipresentes no JavaScript moderno.
Espero que este artigo tenha sido útil para você. Obrigado pela atenção.