Capítulo 1: Clases en JavaScript (Classes)

Luis Casillas
DevEnEspañol
Published in
12 min readJun 7, 2021

Parte de la serie Convertirse en desarrollador

Photo by Sigmund on Unsplash

Introducción

Esta es la primera parte de la serie Convertirse en desarrollador, en el intro o prólogo puedes encontrar más acerca de la metodología, motivación y demás acerca de la serie misma.

Para crear este artículo o capítulo de la serie, visité dos libros y muchos otros recursos que trataré de vincular o mencionar en este material.

Durante mi exploración del material me encontré con una postura muy fuerte que comparto y que veo reflejada en la comunidad de JavaScript que es que las Clases son vistas “hacia abajo” por ponerlo de alguna manera y que trataré de exponer a lo largo de este capítulo. Aún así considero que si bien ya no son tan comunes, tienen asociados algunos conceptos útiles y poderosos. Además una de las maneras más efectivas de mejorar como desarrollador es leer código y es muy probable encontrarnos librerías código reciente escrito con clases.

Clases en JavaScript

Antes que nada, creo que es importante decir que las clases en JavaScript no son realmente una implementación completa de las clases que están implementadas en otros lenguajes Orientados a Objetos (OO por sus siglas en inglés Object Oriented), son más una cortesía que pretende suavizar ya sea la transición o la extensión de aquellos desarrolladores que están más familiarizados con este tipo de patrón de diseño.

En términos generales considero que aprender la sintaxis y patrones relacionados con las Clases en JavaScript ofrecen a quien las aprende más un ejercicio mental que una herramienta, es verdad que se pueden implementar y que la sintaxis permite que generemos Clases en JavaScript, incluso utilizando la palabra “Class” para este propósito, no obstante al no ser una implementación completa no aportan todas las ventajas y sí tienen diversas desventajas. Con esto no quiero necesariamente decir que se deberían dejar de escribir clases o dejarlas de lado por completo, para nada, sin embargo la comunidad se aleja naturalmente de ellas, por ejemplo la documentación de React menciona en su introducción a hooks, que las clases confunden tanto a máquinas como a las personas, un extracto lee:

“In addition to making code reuse and code organization more difficult, we’ve found that classes can be a large barrier to learning React. You have to understand how this works in JavaScript, which is very different from how it works in most languages. You have to remember to bind the event handlers. Without unstable syntax proposals, the code is very verbose. People can understand props, state, and top-down data flow perfectly well but still struggle with classes. The distinction between function and class components in React and when to use each one leads to disagreements even between experienced React developers.”

traducido libremente por su servidor a:

“Además de hacer la reutilización y organización de código más difícil, nos hemos encontrado con que las clases pueden ser una barrera más grande para aprender React. Tienes que entender cómo this funciona en JavaScript, que es muy diferente de cómo funciona en otros lenguajes. Tienes que recordar hacer las ligaturas (bind) de los manejadores de eventos. Sin sintaxis inestable , el código es muy verboso (largo y tedioso). La gente puede entender props, state y manejo de datos direccional arriba-abajo perfectamente y aún luchar con el concepto de clases. La distinción entre componentes funcionales y de clase en React y cuándo usar cada uno ha llevado a desacuerdos aún entre desarrolladores experimentados (de React)”

Lo cual deja claro que son fuente de confusión y tedio, al menos en la comunidad de React que es de las más extensas de desarrolladores de JavaScript.

Este fragmento y en general la exploración propia por la que pasé para poder generar las notas que después darían vida a este artículo dejan claro que el concepto de clase está fuertemente ligado al de this en JavaScript para lo cual crearé un apéndice de la serie. Si eres de los afortunados que está leyendo esto y el artículo ya existe, recomiendo leerlo a la par, en caso de que aún no exista, trataré de ser lo más preciso posible con las explicaciones y ejemplos.

Pero bueno en palabras simples y llanas, la implementación de clases en JS es algo así como la implementación de otros lenguajes pero en realidad no es la implementación de otros lenguajes.

Glosario

this: palabra clave de referencia en JavaScript, en una función se refiere al contexto de ejecución en modo estricto puede ser cualquier cosa, en modo no estricto siempre hace referencia a un objeto (¿confuso?, yo sé, estoy trabajando en un artículo para cubrir este tema)

props: Propiedades que recibe un componente en React.

state: Pieza de información de la que es responsable un componente en React.

binding: Vincular o unir, en JavaScript el método .bind() (ES5) sirve para definir el valor de this independientemente de cómo se ejecute una función.

top-down data flow: Es una manera de describir un flujo de información por medio de usar la manera de escritura, siempre hay que definir una clase antes de extenderla, como ese código está escrito “arriba”, se entiende que la información fluye hacia “abajo”.

interfaces: las interfaces son maneras de comunicación que exponen los elementos en este caso de un sistema y con las que se comunican. Una interfaz para un coche por ejemplo es la de los pedales y el volante, que permiten comunicar nuestras acciones al coche en términos del lenguaje que entiende como dirección, cantidad de gasolina o combustible que recibe, etc.

encapsulación (encapsulation): se refiere a generar cápsulas o partículas independientes con entidades de código, es el principio de generar objetos autónomos.

En general la separación de interfaces e implementación se conoce como encapsulación.

mutar: Cambiar o alterar de alguna manera el valor o la referencia.

instancia: Copia o reproducción de un objeto mediante una función.

Contexto histórico

Las Clases en JavaScript fueron introducidas con el estándar del lenguaje ES6, y es un adorno sintáctico (syntactic sugar), lo cual quiere decir que en realidad la verdadera implementación o implementación subyacente de lo que estamos escribiendo como Clases en JavaScript está siendo interpretado de una manera diferente a la que vemos, algo así como decir “bajo el capó”, “tras bambalinas” o “detrás de cámaras”. El uso extenso de clases y su adopción en los primeros días de React hace que uno piense que no es así, sin embargo, es una realidad que muchas de las ventajas no se pueden implementar tan suavemente.

Antes de la implementación más formal con la palabra clave Class publicada en 2015 con el estándar ES6 de JavaScript, los programadores del lenguaje ya implementaban una suerte de clases por medio de constructor functions (funciones constructores) y prototype delegation o delegación de prototipos

Aquí hay una implementación pre-clases y su contraparte con clases que sigue estos principios:

Y aquí otro ejemplo muy sencillo comparando la sintaxis de clase con la misma implementación funcional:

En realidad lo que hace JavaScript es producir este tipo de código o muy parecido cuando usamos la sintaxis de clases.

Contexto funcional

Las Clases en JavaScript nos permiten utilizar una sintaxis muy parecida a la de otros lenguajes con una implementación completa de clases (Java por ejemplo) y nos permite prestar o utilizar algunos principio de extensibilidad y herencia como patrones de diseño para aplicarlos a la arquitectura de los sistemas a los que nos podamos enfrentar.

Una de las analogías que me gusta para explicar cómo funcionan las clases en JavaScript es la de una receta de cocina, digamos que la Clase es la receta de cocina y cada una de las instancias es digamos el platillo que se creo a raíz de dicha receta, la relación que se describe a raíz de esta analogía comprende muchas de las limitantes y ventajas inherentes o propias de las Clases.

En primera lugar cuando creamos la receta, no necesariamente nos preocupa la cantidad de platillos que se harán, dónde se cocinarán, a quién se servirán o en el caso de que digamos que nuestra receta necesita leche, no nos importa demasiado la marca de la misma que se use para hacer el platillo, a menos que la especifiquemos, si es que fuera importante la marca pero eso haría nuestra receta limitada por naturaleza. La receta tan sólo es una referencia que se usa para hacer el platillo, no significa que lo podamos oler o comer y que guarde alguna referencia con el platillo físico que genera.

Para poder probar dicho platillo habrá que prepararlo o instanciarlo y dicha operación nos permitirá interactuar con la copia, sin que el hecho de que nosotros agreguemos una poca de más o de menos de sal, azúcar o algún otro ingrediente, cambie la recete en alguna manera. Tampoco esperaríamos que la receta fuera parte del platillo es decir que abajo en el molde viniera escrita, sin embargo sería posible preguntarla a quien preparó el platillo (suponiendo que estuviera dispuesta a compartirla, eso es) y que nos la proporcionara. De la misma manera es posible saber de qué Clase proviene o se basó un objeto instanciado.

(figura de receta/animación)

La relación que guarda cualquier clase con los objetos que se crearon a partir de ella es mucho más fuerte que la del objeto generado, un poco como la receta y el platillo, una receta puede valer mucho más que los platillos que genera.

Explicación

La programación orientada a objetos (Object Oriented Programming u OOP por sus siglas en ingles) es un conjunto de técnicas que se pueden utilizar para crear sistemas complejos y programas mediante abstraer la complejidad en objetos que permitan comunicarse entre ellos mediante interfaces.

Estas interfaces tienen la finalidad de esconder implementaciones, además también pueden tener objetos “autónomos” o modulares que permiten que cada objeto se encargue de solo aquella información que le corresponde, esta idea se conoce como encapsulación, la encapsulación se trata de tener diferentes piezas o partes de un programa que se encargan de administrar una parte de la información que compone la funcionalidad de dicho programa a lo largo del tiempo.

Esta parte o partes de la información que maneja cada una de las piezas se conoce como estado local y se dice que es una “propiedad” de esa pieza. En cualquier momento en el que dicha información tenga que mutar (una manera muy técnica de decir cambiar o transformarse), es posible que lo comunique a otras piezas del programa por medio de su interfaz o que lo administre localmente y las demás piezas no tengan que estar conscientes de dicho cambio, ya que no les afecta.

(figura de “estado local” )

Estos objetos tienen generalmente métodos y propiedades que pueden ser públicas o privadas dependiendo de si es necesario para otras piezas conocer o no dicha información y mantenerse informadas de sus cambios.

Este paradigma es un idea bastante poderosa y a pesar de la implementación incompleta en JavaScript, muchos desarrolladores han logrado exitosamente incorporar estos principios en sus arquitecturas. Claro, con sus altibajos o dificultades, muchas veces bien escondidas.

Esta implementación puede llevarse a cabo en JavaScript con Clases, ahora estas clases tienen diferentes complicaciones de las que hablaremos, así como sus equivalencias y algunas ideas sobre otros métodos o paradigmas para tratar de extender o emular el comportamiento que resulta conveniente.

Constructor

Las instancias o copias de una clase en JavaScript son creadas por medio de un constructor, su trabajo es inicializar un objeto con la información o estado que va a necesitar para operar. Se ejecuta al momento de instanciar una clase, hay al menos 5 maneras en total de instanciar un objeto en JavaScript, siendo la sintaxis de clases, el más novedoso. La analogía que me viene a la menta quizá sería la de una computadora o dispositivo nuevo que cuando se prende por primera vez.

(figura de dispositivo nuevo)

Con la siguiente sintaxis entendemos que una clase instancia un nuevo objeto con las propiedades necesarias que proporcionamos al constructor

Métodos

Los métodos son propiedades que guardan funciones en la notación de clases, sirven para que el objeto instanciado pueda administrar la información que tiene almacenada como estado (state).

Existe una propuesta para poder hacer dichos métodos privados y es común verlos denotados con un guión bajo que precede al método para marcarlos o denotarlos como privados aunque realmente no lo sean.

Es importante notar que es posible reescribir o sobreescribir métodos originales del prototipo de objeto como “toString()” con esta notación como se puede ver en el ejemplo de constructor y en este ejemplo.

Herencia

La herencia es un método para extender las clases en términos de información y métodos, sirve para que podamos tener “planos” base que podemos utilizar para crear nuevos planos. Podemos crear por ejemplo una clase base que llamemos “Receta” y luego extenderla a “PastelDeChocolate” en donde almacenamos características adicionales sin la necesidad de volver a escribir las de la clase original. Es importante notar que los métodos originales siempre son accesibles mediante la palabra clave super que nos da la noción de una Súper Clase, o clase madre. Así mismo la palabra super nos permite acceder al constructor original de la clase en la que está basada la subclase.

Además es importante notar el uso de super(…args); al que proporcionamos el argumento que recibe la subclase como argumento inicial para que la clase madre/padre se inicialice correctamente, de otra manera no tendría acceso al nombre que dimos como argumento al instanciar Car con la palabra reservada new. A mí me gusta pensar en super como un alias para el constructor de la clase de la que estamos heredando.

Poliformismo

El poliformismo en la sintaxis de clases de JavaScript se refiere a la referencia relativa que se puede hacer al reescribir un método con un nombre igual al de una clase “superior” o de un nivel superior a la clase sobre la que estamos trabajando, de tal manera que el método que escribimos en la subclase, si tiene el mismo nombre en la superclase cambia (de ahí el término polymorphism) al que hayamos declarado en esa subclase.

Por eso es que podemos “sobreescribir” métodos.

Hago la aclaración de “relativo” porque el método que va a usar la instancia de la clase es el que se haya declarado dentro de ella misma sin la necesidad de referenciar al método que estamos sobreescribiendo, no obstante manteniendo acceso al método original con la palabra super.

Implementación

Notación

Sigue la forma inicial para declarar una clase, opciones adicionales se demuestran a lo largo del capítulo.

Desventajas

  • Vinculación: (binding) siempre hay que recordar hacerla manualmente, lo cual genera un costo de mantenimiento más alto
  • Muchas de las ventajas originales como métodos privados y públicos no están aún implementados, sin embargo hay una propuesta
  • Desempeño: Es difícil optimizarlas al momento de la ejecución
  • Jerarquía estricta: Crea un orden estricto de jerarquización de arriba-hacia-abajo en componentes y eso puede hacer el mantenimiento sea complejo, innecesariamente.
  • En general JavaScript es un lenguaje basado en prototipos y hay otras filosofías de programación más alineadas con el modelo.

Alternativas

  • Mixins
  • Enfoque funcional

Notas

  • De la misma manera que con las funciones (functions) la palabra clase sirve tanto para hacer declaraciones(statements) como expresiones (expressions)

es importante decir que las expresiones de clases pueden tener clases nombradas o anónimas (sin nombre) como en el ejemplo de arriba.

  • Las declaraciones de clases (class declarations) a diferencia de las declaraciones de funciones (function declarations) no son sujetas de hoisting, es decir que no son sujetas de guardarse en memoria durante la fase de compilación, es decir que a diferencia de las function declarations, las “class declarations” no se pueden ejecutar antes de ser declaradas.

El término “hoisting”, sugiere que físicamente el código se reescribe al principio del archivo para poder ser accedido de inmediato (hoist se traduce libremente como levantar), sin embargo se almacena en memoria durante la compilación, en el caso de las clases declaradas no es así.

  • El cuerpo de las clases se ejecuta en modo estricto “strict mode”, es decir que está sujeto a reglas más estrictas que sirven o están orientadas a la optimización
  • Dato “curioso” aunque se siente más obligatorio que otra cosa, es posible que si te topaste con cómo se utilizan las clases en React te diste cuenta que hacemos un extra binding en el constructor, algo así como el siguiente ejemplo y esto es para “Mantener” el valor de this a través de los niveles hacía abajo. Porque el hijo no necesariamente sabe a qué se refiere this cuando lo recibe. Algo así como decirle a alguien “esto” y apuntar a algo que está viendo, luego esa persona va con otra y hace otra referencia a “esto”, ¿Qué es esto para la segunda persona? Por eso la aclaración en código adicional. Espero poder ampliar sobre esta explicación cuando llegue al capítulo de React.

Conclusión

A pesar de que pareciera que el capítulo trata de hablar mal de las clases, no lo es, personalmente no tengo nada en contra porque además, para crear el artículo del funcionamiento de this, me quedó mucho más claro de lo que lo había tenido nunca, además de que todavía es posible encontrarse con componentes de clase y tener que mantenerlos porque reescribirlos es muy tardado o no necesario porque funcionan además de que el soporte está garantizado por parte del equipo de React.

La crítica es más bien el reflejo del material fuente para crear este capítulo, pero además creo que es importante ver el contexto de utilización de las clases y sus críticas no sólo en un contexto técnico sino a la luz de muchas otras realidades del lenguaje, una de ellas siendo que está evolucionando y que es posible que a raíz de una implementación se genere lo que pareciera una mejora objetiva, (por ejemplo, la implementación de la sintaxis de clases en el lenguaje) pueda terminar siendo una decisión de impacto ambiguo. Entre otras cosas creo que haber añadido la sintaxis al lenguaje es una muestra de la inclusividad, del alcance y de la versatilidad del lenguaje, si algo, habla bien de JavaScript haber incorporado algo que pareciera que naturalmente no debería poderse hacer y que además soporta múltiples implementaciones en librerías, frameworks e implementaciones de funcionalidades en general.

Citas y referencias

Simpson, K. (2014). In You Don’t Know JS: this & Object Prototypes. story, O’Reilly Media, Inc.

Haverbeke, M. (2019). In Eloquent JavaScript: a modern introduction to programming. story, No Starch Press.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

https://medium.com/@taylorshephard1/instantiation-patterns-in-javascript-7f9463b95839

--

--

Luis Casillas
DevEnEspañol

Coding Content || Industrial designer turned Software Developer. Co-Founder @n12estudio || 🎮 gaming somewhere