Introducción
JavaScript es un lenguaje basado en prototipos, y cada objeto en JavaScript tiene una propiedad interna oculta llamada [[Prototype]]
que se puede utilizar para ampliar las propiedades y métodos de los objetos.
La especificación del lenguaje ECMAScript 2015, a menudo denominada ES6, introdujo clases en el lenguaje JavaScript. Las clases en JavaScript en realidad no ofrecen ninguna funcionalidad adicional y a menudo se describen como "azúcar sintáctico" sobre los prototipos y la herencia, ya que ofrecen una sintaxis más limpia y elegante. Dado que otros lenguajes de programación usan clases, la sintaxis de clases en JavaScript facilita a los desarrolladores la navegación entre los distintos lenguajes.
Las clases son funciones
Una clase de JavaScript es un tipo de función. Las clases se declaran con la palabra clave class
Usaremos la sintaxis de expresión de función para inicializar una función y la sintaxis de expresión de clase para inicializar una clase.
//Initializing a function with a function expression
const x = function() {}
//Initializing a class with a class expression
const y = class {}
Podemos acceder a [[Prototype]]
de un objeto usando el método Object.getPrototypeOf()
Usémoslo para probar la función vacía que tenemos.
Object.getPrototypeOf(x);
ƒ () { [native code] }
También podemos usar ese método en la clase que acabamos de crear.
Object.getPrototypeOf(y);
ƒ () { [native code] }
El código declarado con function
y class
devuelve una función [[Prototype]]
. Con los prototipos, cualquier función puede convertirse en una instancia del constructor utilizando la new
palabra clave.
const x = function() {}
//Initialize a constructor from a function
const constructorFromFunction = new x();
console.log(constructorFromFunction);
x {}
constructor: ƒ ()
Esto también se aplica a las clases.
const y = class {}
//Initialize a constructor from a class
const constructorFromClass = new y();
console.log(constructorFromClass);
y {}
constructor: class
Estos ejemplos de constructores de prototipos están vacíos, pero podemos ver la sintaxis, ambos métodos obtienen el mismo resultado final.
Definir una clase
Una función constructora se inicializa con una serie de parámetros, que serían asignados como propiedades de this
, haciendo referencia a la propia función. La primera letra del identificador estaría en mayúscula por convención.
Cuando lo traducimos a la sintaxis de la clase, que se muestra a continuación, vemos que está estructurado de manera muy similar.
Sabemos que una función de constructor está destinada a ser un modelo de objeto con la primera letra del inicializador en mayúscula (que es opcional). class
palabra clave comunica más directamente el propósito de nuestra función.
La única diferencia en la sintaxis de inicialización es usar la palabra clave de class
function
asignar propiedades dentro de un método constructor()
Definir los métodos
La práctica común con las funciones de constructores es asignar métodos directamente al prototype
lugar de la inicialización, como se ve en el siguiente método greet()
Con las clases, esta sintaxis se simplifica y el método se puede agregar directamente a la clase. Usando el atajo de definición de método introducido en ES6, definir un método es un proceso aún más conciso.
Echemos un vistazo a estas propiedades y métodos en acción. Crearemos una nueva instancia de Hero
new
palabra clave y asignaremos algunos valores.
const hero1 = new Hero('Varg', 1);
Si imprimimos más información sobre nuestro nuevo objeto con console.log(hero1)
, podemos ver más detalles sobre lo que está sucediendo con la inicialización de la clase.
Hero {name: "Varg", level: 1}
__proto__:
▶ constructor: class Hero
▶ greet: ƒ greet()
Podemos ver en la salida que las funciones constructor()
y greet()
se han aplicado a __proto__
, o [[Prototype]]
de hero1
, y no directamente como un método en hero1
. Si bien esto es claro cuando se crean funciones de constructores, no es obvio cuando se crean clases. Las clases permiten una sintaxis más simple y concisa, pero sacrifican algo de claridad en el proceso.
Ampliar una clase
Una característica ventajosa de las funciones y clases de constructor es que pueden extenderse a nuevos esquemas de objetos basados en padres. Esto evita la repetición de código para objetos que son similares pero requieren alguna funcionalidad adicional o más específica.
El padre puede crear nuevas funciones de constructor utilizando el método call()
En el siguiente ejemplo, crearemos una clase de personaje más específica llamada Mage
y le asignaremos propiedades de Hero
call()
, además de agregar una propiedad adicional.
En este punto, podemos crear una nueva instancia de Mage
usando las mismas propiedades que Hero
y una nueva adición.
const hero2 = new Mage('Lejon', 2, 'Magic Missile');
Al iniciar hero2
desde la consola, podemos ver que hemos creado un nuevo Mage
Mage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__:
▶ constructor: ƒ Mage(name, level, spell)
Con las clases de ES6, la super
se usa en lugar de la call
para acceder a las funciones principales. Usaremos extends
para referirnos a la clase principal (parent).
Ahora podemos crear una nueva Mage
de la misma manera.
const hero2 = new Mage('Lejon', 2, 'Magic Missile');
Imprimiremos hero2
en la consola y veremos el resultado.
Mage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__: Hero
▶ constructor: class Mage
El resultado es casi exactamente el mismo, excepto que en el constructor de la [[Prototype]]
está vinculado al padre, en este caso Hero
.
A continuación se muestra una comparación en paralelo de todo el proceso de inicialización, adición de métodos y herencia de una función de constructor y una clase.
Aunque la sintaxis es bastante diferente, el resultado subyacente es casi el mismo entre ambos métodos. Las clases nos brindan una forma más concisa de crear planos de objetos y las funciones de los constructores describen con mayor precisión lo que está sucediendo.
Conclusión
En este tutorial, aprendimos sobre las similitudes y diferencias entre las funciones del constructor de JavaScript y las clases de ES6. Tanto las clases como los constructores imitan un modelo de herencia orientado a objetos en JavaScript, que es un lenguaje de herencia basado en prototipos.
Comprender la herencia prototípica es fundamental para ser un desarrollador de JavaScript eficaz. Conocer las clases es extremadamente útil, ya que las bibliotecas populares de JavaScript como React hacen un uso frecuente de la sintaxis class