Programación Orientada a Objetos - Clases en ES6
Last updated
Was this helpful?
Last updated
Was this helpful?
En esta sesión veremos el concepto de clases, que está muy relacionado con la (OOP, según sus siglas en inglés). La POO es una manera consensuada de pensar la programación (un ) que se usa en una gran variedad de lenguajes de programación.
En la última sección, veremos cómo reutilizar nuestro código entre distintos proyectos, o importar código ajeno.
JavaScript es un lenguaje muy flexible. Por eso, en la práctica, muchos programadores recurren a otros conceptos asentados de programación al escribir JavaScript. Con ES6 se guiñó un ojo a los programadores que usaban las posibilidades de JavaScript para la POO y se introdujeron las clases.
Las clases nos ayudan a delimitar la responsabilidad de ciertas partes de nuestro código y organizarlo de manera más clara y también nos ayudan a reutilizar partes del código y ahorrar líneas y posibilidades de error.
Ya sabemos lo que es un objeto en programación: es una entidad que contiene unas propiedades dadas, que pueden ser valores o funciones. Cuando tenemos un puñado de objetos parecidos porque tienen las mismas propiedades, podemos decir que esos objetos son del mismo tipo. Es decir, son de la misma "clase". Una clase es justo eso, una abstracción de los objetos que nos indica qué tienen en común.
Una instancia es un objeto de una clase que hayamos especificado. En el ejemplo anterior, hachiko
y laika
son instancias de la clase Dog
. Dándole la vuelta, cuando creamos un objeto de una clase con el operador new
, entonces estamos instanciando una clase. Las instancias comparten los métodos y atributos de la clase.
Los objetos se caracterizan por sus propiedades, que pueden ser funciones o valores. En las clases, llamaremos métodos a las funciones de una clase, y atributos a los valores. Veremos cómo declarar atributos en la sección sobre el constructor. De momento, vamos a declarar un método para la clase Dog
:
Nota: Debes notar que para declarar un método en una clase, no usamos la palabra
function
sino directamente el nombre del método
Vamos a crear un programita que haga cálculos geométricos sencillos. En el programa definiremos la clase Square
, que tendrá al menos:
Un método para calcular el perímetro (perimeter()
) del cuadrado (multiplica la longitud del lado por el número de lados)
Un método para calcular el área (area()
) del cuadrado (elevar al cuadrado el lado)
Los métodos recibirán la longitud del lado (side
) como parámetro.
Calcularemos y mostraremos el perímetro y el área de un cuadrado de 9
de lado.
Como hemos visto, las instancias comparten los métodos de la clase de la que vienen. Pero las instancias también pueden declarar los suyos propios e individuales. En este ejemplo, la instancia hachiko
declara un método waitForOwner
que solo puede usar ella.
Si una instancia declara un método con el mismo nombre que su clase, entonces el método se sobrescribe para esa instancia solo:
this
El constructor()
es un método especial de las clases. El constructor es el método encargado de inicializar la instancia, es decir, de preparar todo lo necesario para su creación. El constructor recibe los parámetros que se pasan al instanciar la clase con new
:
En el constructor, además, es donde se declaran los atributos de la clase. Vamos a declarar el parámetro name
como un atributo:
La palabra clave this
dentro de la declaración de una clase hace referencia a la instancia de la clase que crearemos. Cuando declaramos atributos en el constructor con this.<atributo>
como en el ejemplo anterior, estamos efectivamente declarando que "la instancia resultante (this
) tendrá la propiedad <atributo>
". Una vez creada la instancia, para acceder a los atributos lo hacemos directamente como en el ejemplo, laika.name
.
De igual manera que los declaramos, con this
podemos acceder a esos atributos desde los métodos:
Vamos a mejorar nuestra calculadora geométrica sencilla. En el programa definiremos la clase Square
, que tendrá al menos:
Un atributo para la longitud del lado (side
) del cuadrado
Un método para calcular el perímetro (perimeter()
) del cuadrado (multiplica la longitud del lado por el número de lados - 4)
Un método para calcular el área (area()
) del cuadrado (elevar al cuadrado el lado)
Los métodos no recibirán parámetros, sino que cogerán los datos necesarios de los atributos de la instancia.
Crearemos tres instancias: una con 1
de lado, otra con 3
y otra con 7
. Llamaremos a los dos métodos en todas las instancias.
extends
)Una de las características más potentes de las clases es que podemos crear subclases. Una subclase es una clase que hereda los métodos y atributos de otra clase. De esta manera, podemos hacer clases más concretas cuando nos haga falta sin tener que reescribir partes del código.
Nota: Existe diferente terminología para hablar de herencia. A las subclases también se las llama clases "hija" de una clase "padre" (o "madre"). Las clases de las que se hereda también se llaman clases "base" o superclases.
Para que una clase herede de otra, usamos la palabra clave extends
:
Las subclases tienen un método especial super()
que debe llamarse al principio del constructor (si se escribe). El método super()
llama al constructor de la superclase; así podemos pasar parámetros de la subclase al constructor de la superclase:
Las subclases pueden declarar nuevos métodos y atributos:
Las subclases también pueden sobrescribir métodos de las superclases:
Vamos a mejorar ¡aún más! nuestra calculadora geométrica sencilla. En el programa definiremos la clase Polygon
, Square
y Triangle
para polígolos regulares (todos sus lados miden lo mismo). Ya os imagináis por dónde van los tiros: las clases Square
y Triangle
serán subclases de Polygon
. La clase Polygon
tendrá, al menos:
Un atributo para el número de lados (numberOfSides
)
Dos atributos más: para la longitud de la base (base
) y de la altura (height
)
Un método para calcular el perímetro (perimeter
) (multiplicar la longitud del lado base por el número de lados)
Un método para calcular el área (area
) (multiplicar base por altura)
La subclase Square
tendrá:
Un atributo lado (side
) igual a la base
Un constructor que recibirá exclusivamente la longitud del lado
La subclase Triangle
tendrá:
Un constructor que recibirá base y altura
Un método area()
que sobrescibirá al de la clase base. Devolverá la mitad de lo que devuelva llamar al área de la clase base (super.area() / 2
)
Crearemos dos instancias: un cuadrado de 4
de lado y un triángulo de 4
de base y 3
de altura. Llamaremos a los dos métodos en todas las instancias.
Los getters y setters nos permiten declarar en las clases unos atributos especiales que ejecutan una función.
Por un lado, los getters se ejecutan cuando usemos un atributo. Esto puede ser útil para atributos "calculados" que dependen de los valores de otros atributos:
Por el otro lado, los setters se ejecutan cuando asignemos un nuevo valor a un atributo. Esto puede ser útil para especificar un efecto secundario que tendrá el cambio de valor:
Es una convención usar nombres de atributos precedidos con barra baja (
_atributo
) para diferenciar los atributos de uso interno, que no deben ser usados desde fuera de la declaración.
Vamos a mejorar ¡aún más, siempre más! nuestra calculadora geométrica sencilla. En el programa definiremos la clase Polygon
, Square
para polígonos regulares (todos sus lados miden lo mismo). La clase Square
será subclase de Polygon
y serán iguales a las del ejercicio bonus A.
Aquí viene lo distinto: la clase Square
tendrá un getter y un setter para consultar o modificar el lado.
Desde una subclase podemos acceder a las propiedades de la clase original con
super.propiedad
.
Crearemos una instancia: un cuadrado de 7
de lado. Haremos lo siguiente:
Pediremos el área y la guardaremos en una variable
Cambiaremos el lado del cuadrado por 47
Pediremos de nuevo el área y la guardaremos en otra variable
Compararemos que los valores son distintos. Si son iguales, ¡meeec!
Los módulos nos facilitan dividir nuestro código en pequeñas partes reutilizables. Podemos dividir nuestro código en partes tanto para organizar un proyecto, compartir código entre distintos proyectos nuestros o para usar librerías de terceros.
dog.js:
main.js:
export
Todo lo que hay dentro de un módulo de JavaScript pertenece exclusivamente al módulo por defecto. Nada se puede acceder desde fuera excepto si se exporta. La palabra clave export
nos permite exportar una variable (var
, let
o const
), función o clase que podrá ser importada por otro código más tarde.
Podemos exportar de varias maneras. Podemos exportar individualmente valores que ya hayamos declarado:
module.js:
También podemos exportar todo de una sola vez (como un objeto envoltorio), que mejora la legibilidad del código cuando es extenso:
module.js:
Por último, podemos declarar un valor exportado por defecto, si queremos. Solo puede haber un valor exportado por defecto en cada módulo, y puede o no tener nombre:
module_default-unnamed.js:
module_default-named.js:
import
Para usar código de un módulo, primero tendremos que importarlo en nuestro código. Como es normal en JavaScript, tenemos varias maneras distintas de importar módulos.
Podemos seleccionar, por su nombre, qué valores exportados importar. Importaremos solo uno de la siguiente manera:
main.js:
E importaremos varios valores así:
main.js:
Si queremos cambiarle el nombre a algún valor, lo podemos hacer con as
:
main.js:
También podemos importar todo el contenido de un módulo con *
. Esto nos importa todos los valores dentro de un objeto envoltorio al que debemos darle nombre con as
:
main.js:
Podemos declarar archivos de JavaScript como módulos en el HTML de la siguiente manera:
index.html:
En el ejemplo, declararíamos main.js de esta manera.
Prueba los ejemplos anteriores exportando datos desde un fichero e importándolos desde otros. Asegúrate de entender bien cómo funcionan las rutas para importar/exportar adecuadamente.
Páginas donde se explica en más profundidad los conceptos de esta sesión
Lista de artículos de colaboradores de Mozilla explicando las novedades de ECMAScript 6
Sin embargo, esta forma de trabajo por los navegadores: solo un 62.81% de las últimas versiones de los navegadores lo soporta. Sin embargo, no tendremos ningún problema cuando usemos module bundlers o para compilar nuestro código, y en estos casos no será necesario declarar los módulos en el HTML.
y
(herencia)