En programación orientada a objetos, una clase es una plantilla de código de programa extensible para crear objetos, proporcionando valores iniciales para el estado (variables miembro) e implementaciones de comportamiento (funciones o métodos miembro).
En la práctica, a menudo necesitamos crear muchos objetos del mismo tipo, como usuarios, bienes o lo que sea.
Como ya sabemos por el capítulo Constructor, operador "nuevo", new function
puede ayudar con eso.
Pero en el JavaScript moderno, hay una construcción de "clase" más avanzada, que introduce nuevas características excelentes que son útiles para la programación orientada a objetos.
La sintaxis básica es:
clase MiClase { // métodos de clase constructor() {...} método1() {...} método2() {...} método3() {...} ... }
Luego use new MyClass()
para crear un nuevo objeto con todos los métodos enumerados.
El método constructor()
es llamado automáticamente por new
, por lo que podemos inicializar el objeto allí.
Por ejemplo:
usuario de clase { constructor(nombre) { this.nombre = nombre; } decir Hola() { alerta(este.nombre); } } // Uso: let usuario = nuevo Usuario("Juan"); usuario.sayHola();
Cuando se llama new User("John")
:
Se crea un nuevo objeto.
El constructor
se ejecuta con el argumento dado y lo asigna a this.name
.
…Entonces podemos llamar a métodos de objeto, como user.sayHi()
.
Sin coma entre métodos de clase.
Un error común para los desarrolladores novatos es poner una coma entre los métodos de clase, lo que provocaría un error de sintaxis.
La notación aquí no debe confundirse con los literales de objetos. Dentro de la clase, no se requieren comas.
Entonces, ¿qué es exactamente una class
? Esta no es una entidad completamente nueva a nivel del lenguaje, como podría pensarse.
Revelemos cualquier magia y veamos qué es realmente una clase. Eso ayudará a comprender muchos aspectos complejos.
En JavaScript, una clase es un tipo de función.
Aquí, echa un vistazo:
usuario de clase { constructor(nombre) { this.nombre = nombre; } diHola() { alerta(este.nombre); } } // prueba: el usuario es una función alerta (tipo de usuario); // función
Lo que realmente hace la construcción class User {...}
es:
Crea una función llamada User
, que se convierte en el resultado de la declaración de clase. El código de la función se toma del método constructor
(se supone vacío si no escribimos dicho método).
Almacena métodos de clase, como sayHi
, en User.prototype
.
Después de crear new User
, cuando llamamos a su método, se toma del prototipo, tal como se describe en el capítulo F.prototype. Entonces el objeto tiene acceso a los métodos de clase.
Podemos ilustrar el resultado de la declaración class User
como:
Aquí está el código para realizar una introspección:
usuario de clase { constructor(nombre) { this.nombre = nombre; } diHola() { alerta(este.nombre); } } // la clase es una función alerta (tipo de usuario); // función // ...o, más precisamente, el método constructor alerta(Usuario === Usuario.prototipo.constructor); // verdadero // Los métodos están en User.prototype, por ejemplo: alerta(Usuario.prototipo.decirHola); // el código del método sayHi // hay exactamente dos métodos en el prototipo alerta(Object.getOwnPropertyNames(Usuario.prototipo)); // constructor, di Hola
A veces la gente dice que class
es un “azúcar sintáctico” (sintaxis diseñada para hacer las cosas más fáciles de leer, pero que no introduce nada nuevo), porque en realidad podríamos declarar lo mismo sin usar la palabra clave class
en absoluto:
// reescribiendo la clase Usuario en funciones puras // 1. Crear función constructora función Usuario (nombre) { this.nombre = nombre; } // un prototipo de función tiene la propiedad "constructor" por defecto, // entonces no necesitamos crearlo // 2. Agrega el método al prototipo Usuario.prototipo.sayHola = función() { alerta(este.nombre); }; // Uso: let usuario = nuevo Usuario("Juan"); usuario.sayHola();
El resultado de esta definición es aproximadamente el mismo. Entonces, existen razones por las que class
puede considerarse un azúcar sintáctico para definir un constructor junto con sus métodos prototipo.
Aun así, existen diferencias importantes.
Primero, una función creada por class
está etiquetada por una propiedad interna especial [[IsClassConstructor]]: true
. Entonces no es del todo lo mismo que crearlo manualmente.
El lenguaje busca esa propiedad en una variedad de lugares. Por ejemplo, a diferencia de una función normal, se debe llamar con new
:
usuario de clase { constructor() {} } alerta (tipo de usuario); // función Usuario(); // Error: el usuario constructor de clase no puede invocarse sin 'nuevo'
Además, una representación de cadena de un constructor de clases en la mayoría de los motores de JavaScript comienza con la "clase..."
usuario de clase { constructor() {} } alerta(Usuario); // clase Usuario {...}
Hay otras diferencias, las veremos pronto.
Los métodos de clase no son enumerables. Una definición de clase establece el indicador enumerable
en false
para todos los métodos del "prototype"
.
Eso es bueno, porque si for for..in
sobre un objeto, generalmente no queremos sus métodos de clase.
Las clases siempre use strict
. Todo el código dentro de la construcción de clase está automáticamente en modo estricto.
Además, la sintaxis class
aporta muchas otras características que exploraremos más adelante.
Al igual que las funciones, las clases se pueden definir dentro de otra expresión, transmitirse, devolverse, asignarse, etc.
A continuación se muestra un ejemplo de una expresión de clase:
dejar Usuario = clase { decir Hola() { alerta("Hola"); } };
De manera similar a las expresiones de funciones con nombre, las expresiones de clase pueden tener un nombre.
Si una expresión de clase tiene un nombre, es visible sólo dentro de la clase:
// "Expresión de clase con nombre" // (no existe tal término en la especificación, pero es similar a la expresión de función nombrada) let Usuario = clase MiClase { decir Hola() { alerta(MiClase); // El nombre de MyClass es visible sólo dentro de la clase } }; nuevo Usuario().sayHola(); // funciona, muestra la definición de MyClass alerta(MiClase); // error, el nombre de MyClass no es visible fuera de la clase
Incluso podemos hacer clases dinámicamente “bajo demanda”, así:
función crearClase(frase) { // declara una clase y la devuelve clase de retorno { decir Hola() { alerta(frase); } }; } // Crea una nueva clase let Usuario = makeClass("Hola"); nuevo Usuario().sayHola(); // Hola
Al igual que los objetos literales, las clases pueden incluir captadores/definidores, propiedades calculadas, etc.
Aquí hay un ejemplo de user.name
implementado usando get/set
:
usuario de clase { constructor(nombre) { // invoca al configurador this.nombre = nombre; } obtener nombre() { devolver this._name; } establecer nombre (valor) { if (valor.longitud < 4) { alert("El nombre es demasiado corto."); devolver; } this._name = valor; } } let usuario = nuevo Usuario("Juan"); alerta(nombre.usuario); // John usuario = nuevo Usuario(""); // El nombre es demasiado corto.
Técnicamente, dicha declaración de clase funciona mediante la creación de captadores y definidores en User.prototype
.
Aquí hay un ejemplo con un nombre de método calculado usando corchetes [...]
:
usuario de clase { ['decir' + 'Hola']() { alerta("Hola"); } } nuevo Usuario().sayHola();
Estas características son fáciles de recordar, ya que se parecen a las de los objetos literales.
Los navegadores antiguos pueden necesitar un polyfill
Los campos de clase son una adición reciente al idioma.
Anteriormente, nuestras clases solo tenían métodos.
"Campos de clase" es una sintaxis que permite agregar cualquier propiedad.
Por ejemplo, agreguemos la propiedad name
a class User
:
usuario de clase { nombre = "Juan"; decir Hola() { alert(`¡Hola, ${this.name}!`); } } nuevo Usuario().sayHola(); // ¡Hola, Juan!
Entonces, simplemente escribimos “
La diferencia importante de los campos de clase es que se configuran en objetos individuales, no User.prototype
:
usuario de clase { nombre = "Juan"; } dejar usuario = nuevo Usuario(); alerta(nombre.usuario); // John alerta(Usuario.prototipo.nombre); // indefinido
También podemos asignar valores usando expresiones más complejas y llamadas a funciones:
usuario de clase { nombre = solicitud("Nombre, por favor?", "Juan"); } dejar usuario = nuevo Usuario(); alerta(nombre.usuario); // John
Como se demostró en el capítulo Las funciones de enlace de funciones en JavaScript tienen una dinámica this
. Depende del contexto de la llamada.
Entonces, si un método de objeto se pasa y se llama en otro contexto, this
no será una referencia a su objeto.
Por ejemplo, este código mostrará undefined
:
Botón de clase { constructor(valor) { this.value = valor; } hacer clic() { alerta (este valor); } } botón let = nuevo botón("hola"); setTimeout(botón.hacer clic, 1000); // indefinido
El problema se llama “perder this
”.
Hay dos métodos para solucionarlo, como se analiza en el capítulo Vinculación de funciones:
Pase una función contenedora, como setTimeout(() => button.click(), 1000)
.
Vincula el método al objeto, por ejemplo, en el constructor.
Los campos de clase proporcionan otra sintaxis bastante elegante:
Botón de clase { constructor(valor) { this.value = valor; } haga clic = () => { alerta (este valor); } } botón let = nuevo botón("hola"); setTimeout(botón.hacer clic, 1000); // Hola
El campo de clase click = () => {...}
se crea por objeto, hay una función separada para cada objeto Button
, y this
dentro hace referencia a ese objeto. Podemos pasar button.click
a cualquier lugar, y el valor de this
siempre será correcto.
Esto es especialmente útil en el entorno del navegador, para los detectores de eventos.
La sintaxis de clase básica se ve así:
clase MiClase { propiedad = valor; // propiedad constructor(...) { // constructor //... } método(...) {} // método obtener algo(...) {} // método getter establecer algo(...) {} // método de establecimiento [Symbol.iterator]() {} // método con nombre calculado (símbolo aquí) //... }
MyClass
es técnicamente una función (la que proporcionamos como constructor
), mientras que los métodos, captadores y definidores se escriben en MyClass.prototype
.
En los próximos capítulos aprenderemos más sobre las clases, incluida la herencia y otras características.
importancia: 5
La clase Clock
(ver el sandbox) está escrita en estilo funcional. Vuelva a escribirlo en la sintaxis de "clase".
PD: El reloj hace tictac en la consola, ábrela para ver.
Abra una zona de pruebas para la tarea.
reloj de clase { constructor({plantilla}) { this.template = plantilla; } prestar() { let fecha = nueva fecha(); let horas = fecha.getHours(); si (horas < 10) horas = '0' + horas; let mins = fecha.getMinutes(); si (minutos < 10) minutos = '0' + minutos; let segundos = fecha.getSeconds(); si (segundos < 10) segundos = '0' + segundos; dejar salida = this.template .replace('h', horas) .replace('m', minutos) .replace('s', segundos); console.log(salida); } detener() { clearInterval(este.temporizador); } comenzar() { this.render(); this.timer = setInterval(() => this.render(), 1000); } } let reloj = nuevo Reloj({plantilla: 'h:m:s'}); reloj.start();
Abra la solución en una caja de arena.