También podemos asignar un método a la clase en su conjunto. Estos métodos se denominan estáticos .
En una declaración de clase, se les antepone una palabra clave static
, como esta:
usuario de clase { método estático estático() { alerta(este === Usuario); } } Usuario.staticMethod(); // verdadero
En realidad, eso hace lo mismo que asignarlo directamente como propiedad:
clase Usuario { } Usuario.método estático = función() { alerta(este === Usuario); }; Usuario.staticMethod(); // verdadero
El valor de this
en la llamada User.staticMethod()
es el propio constructor de la clase User
(la regla del "objeto antes del punto").
Por lo general, los métodos estáticos se utilizan para implementar funciones que pertenecen a la clase en su conjunto, pero no a ningún objeto particular de ella.
Por ejemplo, tenemos objetos Article
y necesitamos una función para compararlos.
Una solución natural sería agregar el método estático Article.compare
:
artículo de clase { constructor(título, fecha) { this.title = título; this.date = fecha; } comparación estática (artículo A, artículo B) { devolver artículoA.fecha - artículoB.fecha; } } // uso dejar artículos = [ nuevo artículo("HTML", nueva fecha(2019, 1, 1)), nuevo artículo("CSS", nueva fecha(2019, 0, 1)), nuevo artículo("JavaScript", nueva fecha(2019, 11, 1)) ]; artículos.sort(Artículo.comparar); alerta( artículos[0].título ); // CSS
Aquí el método Article.compare
se sitúa "encima" de los artículos, como medio para compararlos. No es un método de un artículo, sino de toda la clase.
Otro ejemplo sería el llamado método "de fábrica".
Digamos que necesitamos varias formas de crear un artículo:
Crear según los parámetros dados ( title
, date
, etc.).
Crea un artículo vacío con la fecha de hoy.
…o de alguna manera.
La primera forma puede ser implementada por el constructor. Y para el segundo podemos hacer un método estático de la clase.
Como por ejemplo Article.createTodays()
aquí:
artículo de clase { constructor(título, fecha) { this.title = título; this.date = fecha; } crearHoyes estático() { // recuerda, esto = Artículo return new this("Resumen de hoy", nueva fecha()); } } let artículo = Artículo.createTodays(); alerta (artículo.título); // resumen de hoy
Ahora, cada vez que necesitemos crear un resumen de hoy, podemos llamar Article.createTodays()
. Una vez más, ese no es un método de un artículo, sino un método de toda la clase.
Los métodos estáticos también se utilizan en clases relacionadas con bases de datos para buscar/guardar/eliminar entradas de la base de datos, como este:
// asumiendo que Artículo es una clase especial para gestionar artículos // método estático para eliminar el artículo por id: Artículo.remove({id: 12345});
Los métodos estáticos no están disponibles para objetos individuales
Los métodos estáticos se pueden invocar en clases, no en objetos individuales.
Por ejemplo, dicho código no funcionará:
//... artículo.createTodays(); /// Error: artículo.createTodays no es una función
Una adición reciente
Esta es una adición reciente al idioma. Los ejemplos funcionan en el Chrome reciente.
Las propiedades estáticas también son posibles, parecen propiedades de clase normales, pero precedidas por static
:
artículo de clase { editor estático = "Ilya Kantor"; } alerta (artículo.editor); // Ilya Kantor
Eso es lo mismo que una asignación directa al Article
:
Artículo.publisher = "Ilya Kantor";
Las propiedades y métodos estáticos se heredan.
Por ejemplo, Animal.compare
y Animal.planet
en el código siguiente se heredan y son accesibles como Rabbit.compare
y Rabbit.planet
:
clase Animal { planeta estático = "Tierra"; constructor(nombre, velocidad) { this.speed = velocidad; this.nombre = nombre; } correr (velocidad = 0) { esta.velocidad += velocidad; alert(`${this.name} se ejecuta con velocidad ${this.speed}.`); } comparación estática (animalA, animalB) { devolver velocidad animalA - velocidad animalB; } } // Heredar de animal clase Conejo extiende Animal { esconder() { alert(`${this.name} se esconde!`); } } dejar conejos = [ nuevo Conejo("Conejo Blanco", 10), nuevo Conejo("Conejo Negro", 5) ]; conejos.sort(Conejo.comparar); conejos[0].run(); // Black Rabbit corre con velocidad 5. alerta(Conejo.planeta); // Tierra
Ahora, cuando llamemos Rabbit.compare
, se llamará al Animal.compare
heredado.
¿Cómo funciona? De nuevo, utilizando prototipos. Como ya habrás adivinado, extends
le da Rabbit
la referencia [[Prototype]]
a Animal
.
Entonces, Rabbit extends Animal
crea dos referencias [[Prototype]]
:
La función Rabbit
hereda prototípicamente de la función Animal
.
Rabbit.prototype
hereda prototípicamente de Animal.prototype
.
Como resultado, la herencia funciona tanto para métodos regulares como estáticos.
Aquí, verifiquemos eso por código:
clase animal {} clase Conejo extiende Animal {} // para estática alerta(Conejo.__proto__ === Animal); // verdadero // para métodos regulares alerta(Conejo.prototipo.__proto__ === Animal.prototipo); // verdadero
Los métodos estáticos se utilizan para la funcionalidad que pertenece a la clase "en su conjunto". No se relaciona con una instancia de clase concreta.
Por ejemplo, un método de comparación Article.compare(article1, article2)
o un método de fábrica Article.createTodays()
.
Están etiquetados con la palabra static
en la declaración de clase.
Las propiedades estáticas se utilizan cuando queremos almacenar datos a nivel de clase, y tampoco están vinculados a una instancia.
La sintaxis es:
clase MiClase { propiedad estática =...; método estático() { ... } }
Técnicamente, una declaración estática es lo mismo que asignar a la clase misma:
MiClase.propiedad = ... MiClase.método = ...
Las propiedades y métodos estáticos se heredan.
Para class B extends A
el prototipo de la clase B
apunta a A
: B.[[Prototype]] = A
. Entonces, si un campo no se encuentra en B
, la búsqueda continúa en A
importancia: 3
Como sabemos, todos los objetos normalmente heredan de Object.prototype
y obtienen acceso a métodos de objetos "genéricos" como hasOwnProperty
, etc.
Por ejemplo:
clase Conejo { constructor(nombre) { this.nombre = nombre; } } let conejo = new Conejo("Rab"); // el método hasOwnProperty es de Object.prototype alerta( conejo.hasOwnProperty('nombre') ); // verdadero
Pero si lo escribimos explícitamente como "class Rabbit extends Object"
, ¿entonces el resultado sería diferente de una simple "class Rabbit"
?
¿Cuál es la diferencia?
A continuación se muestra un ejemplo de dicho código (no funciona, ¿por qué? ¿arreglarlo?):
clase Conejo extiende Objeto { constructor(nombre) { this.nombre = nombre; } } let conejo = new Conejo("Rab"); alerta( conejo.hasOwnProperty('nombre') ); // Error
Primero, veamos por qué el último código no funciona.
La razón se vuelve obvia si intentamos ejecutarlo. Un constructor de clase heredero debe llamar super()
. De lo contrario, "this"
no estará "definido".
Así que aquí está la solución:
clase Conejo extiende Objeto { constructor(nombre) { súper(); // es necesario llamar al constructor padre al heredar this.nombre = nombre; } } let conejo = new Conejo("Rab"); alerta( conejo.hasOwnProperty('nombre') ); // verdadero
Pero eso no es todo todavía.
Incluso después de la solución, todavía hay una diferencia importante entre "class Rabbit extends Object"
y class Rabbit
.
Como sabemos, la sintaxis “extiende” configura dos prototipos:
Entre el "prototype"
de las funciones constructoras (para métodos).
Entre las funciones constructoras (para métodos estáticos).
En el caso de class Rabbit extends Object
significa:
clase Conejo extiende Objeto {} alerta (Conejo.prototipo.__proto__ === Objeto.prototipo); // (1) verdadero alerta (Conejo.__proto__ === Objeto); // (2) verdadero
Entonces Rabbit
ahora proporciona acceso a los métodos estáticos de Object
a través de Rabbit
, así:
clase Conejo extiende Objeto {} // normalmente llamamos a Object.getOwnPropertyNames alerta ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); //a,b
Pero si no tenemos extends Object
, entonces Rabbit.__proto__
no está configurado en Object
.
Aquí está la demostración:
clase Conejo {} alerta (Conejo.prototipo.__proto__ === Objeto.prototipo); // (1) verdadero alerta (Conejo.__proto__ === Objeto); // (2) falso (!) alerta (Conejo.__proto__ === Función.prototipo); // como cualquier función por defecto // error, no existe tal función en Rabbit alerta ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error
Entonces Rabbit
no proporciona acceso a métodos estáticos de Object
en ese caso.
Por cierto, Function.prototype
también tiene métodos de función "genéricos", como call
, bind
, etc. En última instancia, están disponibles en ambos casos, porque para el constructor Object
integrado, Object.__proto__ === Function.prototype
.
Aquí está la imagen:
Entonces, para resumir, hay dos diferencias:
clase conejo | clase Conejo extiende Objeto |
---|---|
– | necesita llamar super() en el constructor |
Rabbit.__proto__ === Function.prototype | Rabbit.__proto__ === Object |