Também podemos atribuir um método à classe como um todo. Esses métodos são chamados estáticos .
Em uma declaração de classe, eles são acrescentados por palavras-chave static
, assim:
classe Usuário { método estático estático() { alerta(isto === Usuário); } } User.staticMethod(); // verdadeiro
Na verdade, isso é o mesmo que atribuí-lo diretamente como uma propriedade:
classe Usuário { } User.staticMethod = function() { alerta(isto === Usuário); }; User.staticMethod(); // verdadeiro
O valor this
na chamada User.staticMethod()
é o próprio construtor da classe User
(a regra “objeto antes do ponto”).
Normalmente, métodos estáticos são usados para implementar funções que pertencem à classe como um todo, mas não a nenhum objeto específico dela.
Por exemplo, temos objetos Article
e precisamos de uma função para compará-los.
Uma solução natural seria adicionar o método estático Article.compare
:
classe Artigo { construtor(título, data) { este.título = título; esta.data = data; } comparação estática (artigoA, artigoB) { retornar artigoA.data - artigoB.data; } } // uso deixe artigos = [ novo artigo("HTML", nova data(2019, 1, 1)), novo artigo("CSS", nova data(2019, 0, 1)), novo artigo("JavaScript", nova data(2019, 11, 1)) ]; artigos.sort(Artigo.comparar); alerta(artigos[0].título); //CSS
Aqui o método Article.compare
fica “acima” dos artigos, como um meio de compará-los. Não é um método de um artigo, mas sim de toda a turma.
Outro exemplo seria o chamado método de “fábrica”.
Digamos que precisamos de várias maneiras de criar um artigo:
Crie por determinados parâmetros ( title
, date
, etc.).
Crie um artigo vazio com a data de hoje.
…ou então de alguma forma.
A primeira forma pode ser implementada pelo construtor. E para o segundo podemos fazer um método estático da classe.
Como Article.createTodays()
aqui:
classe Artigo { construtor(título, data) { este.título = título; esta.data = data; } static createHoje() { // lembre-se, isto = Artigo return new this("Resumo de hoje", new Date()); } } deixe artigo = Artigo.createTodays(); alerta(artigo.título); // Resumo de hoje
Agora, toda vez que precisarmos criar um resumo de hoje, podemos chamar Article.createTodays()
. Mais uma vez, este não é um método de um artigo, mas um método de toda a classe.
Métodos estáticos também são usados em classes relacionadas ao banco de dados para pesquisar/salvar/remover entradas do banco de dados, como este:
// assumindo que Article é uma classe especial para gerenciar artigos // método estático para remover o artigo por id: Artigo.remove({id: 12345});
Métodos estáticos não estão disponíveis para objetos individuais
Os métodos estáticos podem ser chamados em classes, não em objetos individuais.
Por exemplo, esse código não funcionará:
// ... artigo.createTodays(); /// Erro: article.createTodays não é uma função
Uma adição recente
Esta é uma adição recente ao idioma. Os exemplos funcionam no Chrome recente.
Propriedades estáticas também são possíveis, elas se parecem com propriedades de classe regulares, mas são anexadas por static
:
classe Artigo { editor estático = "Ilya Kantor"; } alerta (artigo.editor); // Ilya Kantor
Isso é o mesmo que uma atribuição direta a Article
:
Artigo.publisher = "Ilya Kantor";
Propriedades e métodos estáticos são herdados.
Por exemplo, Animal.compare
e Animal.planet
no código abaixo são herdados e acessíveis como Rabbit.compare
e Rabbit.planet
:
classe Animal { planeta estático = "Terra"; construtor(nome,velocidade){ this.speed = velocidade; este.nome = nome; } correr(velocidade = 0) { this.speed += velocidade; alert(`${this.name} roda com velocidade ${this.speed}.`); } comparação estática(animalA, animalB) { retornar animalA.velocidade - animalB.velocidade; } } //Herdar do Animal classe Coelho estende Animal { esconder() { alert(`${this.name} esconde!`); } } deixe coelhos = [ novo Coelho("Coelho Branco", 10), novo Coelho("Coelho Preto", 5) ]; coelhos.sort(Coelho.compare); coelhos[0].run(); // Black Rabbit corre com velocidade 5. alerta(Coelho.planeta); // Terra
Agora, quando chamarmos Rabbit.compare
, o Animal.compare
herdado será chamado.
Como funciona? Novamente, usando protótipos. Como você já deve ter adivinhado, extends
dá Rabbit
a referência [[Prototype]]
para Animal
.
Então, Rabbit extends Animal
cria duas referências [[Prototype]]
:
A função Rabbit
herda prototipicamente da função Animal
.
Rabbit.prototype
herda prototipicamente de Animal.prototype
.
Como resultado, a herança funciona tanto para métodos regulares quanto para métodos estáticos.
Aqui, vamos verificar isso por código:
classe Animal {} classe Coelho estende Animal {} //para estática alerta(Coelho.__proto__ === Animal); // verdadeiro // para métodos regulares alerta(Coelho.prototype.__proto__ === Animal.prototype); // verdadeiro
Métodos estáticos são usados para a funcionalidade que pertence à classe “como um todo”. Não se relaciona a uma instância de classe concreta.
Por exemplo, um método de comparação Article.compare(article1, article2)
ou um método de fábrica Article.createTodays()
.
Eles são rotulados pela palavra static
na declaração de classe.
Propriedades estáticas são usadas quando queremos armazenar dados em nível de classe, também não vinculados a uma instância.
A sintaxe é:
class MinhaClasse { propriedade estática = ...; método estático() { ... } }
Tecnicamente, a declaração estática é o mesmo que atribuir à própria classe:
MinhaClasse.property = ... MinhaClasse.method = ...
Propriedades e métodos estáticos são herdados.
Para class B extends A
o protótipo da própria classe B
aponta para A
: B.[[Prototype]] = A
. Portanto, se um campo não for encontrado em B
, a busca continua em A
.
importância: 3
Como sabemos, todos os objetos normalmente herdam de Object.prototype
e têm acesso a métodos de objeto “genéricos” como hasOwnProperty
etc.
Por exemplo:
classe Coelho { construtor(nome) { este.nome = nome; } } deixe coelho = new Coelho("Rab"); // O método hasOwnProperty é de Object.prototype alerta(coelho.hasOwnProperty('nome') ); // verdadeiro
Mas se enunciarmos explicitamente como "class Rabbit extends Object"
, então o resultado seria diferente de uma simples "class Rabbit"
?
Qual é a diferença?
Aqui está um exemplo desse código (não funciona – por quê? consertar?):
classe Coelho estende Objeto { construtor(nome) { este.nome = nome; } } deixe coelho = new Coelho("Rab"); alerta(coelho.hasOwnProperty('nome') ); // Erro
Primeiro, vamos ver por que o último código não funciona.
A razão se torna óbvia se tentarmos executá-lo. Um construtor de classe herdada deve chamar super()
. Caso contrário, "this"
não será “definido”.
Então aqui está a solução:
classe Coelho estende Objeto { construtor(nome) { super(); // precisa chamar o construtor pai ao herdar este.nome = nome; } } deixe coelho = new Coelho("Rab"); alerta(coelho.hasOwnProperty('nome') ); // verdadeiro
Mas isso ainda não é tudo.
Mesmo após a correção, ainda há uma diferença importante entre "class Rabbit extends Object"
e class Rabbit
.
Como sabemos, a sintaxe “extends” configura dois protótipos:
Entre o "prototype"
das funções construtoras (para métodos).
Entre as próprias funções construtoras (para métodos estáticos).
No caso da class Rabbit extends Object
significa:
classe Rabbit estende objeto {} alerta( Rabbit.prototype.__proto__ === Object.prototype ); // (1) verdadeiro alerta(Coelho.__proto__ === Objeto); // (2) verdadeiro
Então Rabbit
agora fornece acesso aos métodos estáticos de Object
via Rabbit
, assim:
classe Rabbit estende objeto {} // normalmente chamamos Object.getOwnPropertyNames alerta ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); //a,b
Mas se não tivermos extends Object
, então Rabbit.__proto__
não será definido como Object
.
Aqui está a demonstração:
classe Coelho {} alerta( Rabbit.prototype.__proto__ === Object.prototype ); // (1) verdadeiro alerta(Coelho.__proto__ === Objeto); // (2) falso (!) alerta( Rabbit.__proto__ === Function.prototype ); // como qualquer função por padrão // erro, tal função não existe no Rabbit alerta ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Erro
Portanto, Rabbit
não fornece acesso a métodos estáticos de Object
nesse caso.
A propósito, Function.prototype
também possui métodos de função “genéricos”, como call
, bind
etc. Eles estão disponíveis em ambos os casos, porque para o construtor Object
integrado, Object.__proto__ === Function.prototype
.
Aqui está a foto:
Então, para resumir, existem duas diferenças:
classe Coelho | classe Rabbit estende objeto |
---|---|
– | precisa chamar super() no construtor |
Rabbit.__proto__ === Function.prototype | Rabbit.__proto__ === Object |