0% encontró este documento útil (0 votos)
213 vistas73 páginas

Sprint 8

Cargado por

Jorge Sepulveda
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
213 vistas73 páginas

Sprint 8

Cargado por

Jorge Sepulveda
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 73

Sprint 8

Capítulo 1/10
Intro

Introducción
El centro comunitario de TripleTen se está construyendo, a paso lento pero
seguro. Y hemos conseguido que sea lo suficientemente seguro para que los
niños de la zona vengan a jugar, y es por esto por lo que los voluntarios han
montado un humilde pero agradable parque infantil. Qué casualidad: hoy, el
arquitecto Amir tiene que cuidar a su sobrina, Nabila. Amir ha venido temprano a
trabajar para esperar a que los padres de Nabila la trajeran y de paso asegurarse
de que el parque infantil estuviese listo para todo lo que la mente creativa de la
niña pudiese imaginar. Al igual que Nabila, estás a punto de probar un nuevo
ámbito en el que jugar: el "sandbox" de las clases de JavaScript.

¿ De qué trata este sprint?


Hasta ahora, has estado programando sin aplicar ningún principio de
organización. Sin embargo, dominar estas estrategias es algo crucial para un
desarrollador web profesional. Adquirir esta habilidad es tan importante porque
te permitirá escribir un código que sea más fácil de leer y de mantener. Este será
el principal objetivo de este sprint. Aprenderás conceptos importantes tales como
la programación orientada a objetos, la encapsulación y los módulos, así como la
forma de implementarlos en JavaScript.

¿Qué habilidades obtendrás?


Aprenderás a dividir la funcionalidad de una página web en entidades llamadas
clases, y a crear y organizar estas clases en JavaScript. Esto te ayudará a asegurar
que las diferentes partes de tu código tienen roles lógicos claros, y también hará
que el mantenimiento de tu código sea más fácil. ¿Estás listo? ¡Vamos allá!

Sprint 8
Capítulo 2/10
Convirtiéndote en desarrollador de front-end
Convirtiéndote en desarrollador de front-
end
¡Felicidades, has llegado a la mitad del programa! Para celebrarlo, echemos un vistazo
desde una perspectiva general.

Hace un tiempo, te hablamos de algunas opciones disponibles para tu carrera en el ámbito


tecnológico del desarrollo web: roles de front-end, roles de back-end, o un rol como
desarrollador web full stack. Ahora, hablemos más sobre ser un desarrollador de front-end
y lo que se puede esperar de un puesto así.
Llevas tanto tiempo aprendiendo y practicando el desarrollo web en TripleTen que lo
natural es que te preguntes cuál sería tu valor actual en el mercado. ¿Qué tareas podrías
hacer? ¿Para qué puestos podrías solicitar empleo?
Cuando empiezas como ingeniero front-end junior o de nivel medio, probablemente te
unirás a un equipo con otros desarrolladores. Esto te dará la oportunidad de mejorar tus
habilidades. También es posible que te incorpores a una empresa más pequeña que ya ha
creado una aplicación y será tu responsabilidad mejorarla. Por ejemplo, puede que tengas
que añadir nuevas funciones, realizar actualizaciones, corregir bugs, etc.
Deberías ser capaz de hacer todo lo anterior, y quizás también de reescribir aplicaciones
antiguas utilizando tecnologías más recientes.
Independientemente del tamaño de tu equipo, en este momento, para conseguir un ascenso,
lo mejor es centrarse en las siguientes habilidades:
 Entender cómo la calidad de tu código puede afectar a los plazos de desarrollo. Las
líneas de código escritas por hora no son una buena métrica para medir la
productividad.
 Comprender cómo preparar tu código para el futuro. Si tu equipo de
producción/gerente/jefe de equipo te pide que implementes una función, ¿hay
alguna forma de hacerlo en la que puedas resolver problemas que surjan más
adelante?
 Aprender a pensar de forma crítica sobre cómo puede fallar tu código; puede ser
difícil corregir errores en un trozo de código que acabas de escribir. Cuando está
recién hecho, es posible que solo puedas pensar en cómo resolver un problema
exactamente como te lo piden, pero los usuarios siempre pueden encontrar maneras
de provocar bugs. Invariablemente, algo saldrá mal y ser capaz de ver dónde y
cuándo es una habilidad que se adquiere con experiencia.

Los salarios oscilan entre los 50.000 y los 70.000 dólares, dependiendo de la zona
geográfica, el tamaño de la empresa y la experiencia previa.
Una progresión natural a partir de este punto sería convertirse en un desarrollador front-end
de nivel medio, luego en un ingeniero de software, y después en un ingeniero de software
senior. Dicho esto, al añadir habilidades de back-end (que lo harás, un poco más adelante
en este programa), esencialmente duplicas tu valor de mercado, ¡convirtiéndote en un
desarrollador full stack en el proceso!
Los próximos tres sprints se dedicarán a seguir desarrollando las habilidades que
constituyen el núcleo de cualquier papel de desarrollador web, especialmente en el front-
end.

Sprint 8
Capítulo 3/10
Programación Orientada a Objetos
Introducción a la programación orientada
a objetos
De mayor, Nabila quiere ser arquitecta como su tío. Para el primer proyecto de su porfolio,
va a construir una maqueta del centro comunitario con lego. Empezó sacando todos los
bloques de la caja e improvisando con ellos, pero eso no acabó del todo bien. Para ayudar a
su sobrina, Amir le entregó un manual detallado. Esto también se aplica a temas más
complejos como la programación orientada a objetos (POO), es importante aprender la
teoría antes de pasar a la práctica. Esto es exactamente lo que vas a tratar en este capítulo,
aprender sobre los principios fundamentales de POO: encapsulación, herencia y
polimorfismo.

A medida que el código dentro de tu programa crece, se hace más difícil añadir
funcionalidades, corregir errores o trabajar en general. Tal vez ya lo hayas notado mientras
trabajabas en tus proyectos.
En el pasado, los programadores escribían código destinado a ser leído únicamente por los
ordenadores. Pero, con el paso del tiempo, surgió la necesidad de organizar el código de
forma que también permitiera trabajar con él. Para ello, se determinó que, en lugar de ser
una simple secuencia de comandos, el código debía estructurarse en bloques lógicos.
Así que, los programadores empezaron a darle vueltas sobre cómo hacerlo y así fue como
aparecieron los paradigmas de programación. Un paradigma de programación es un
enfoque de la codificación basado en un conjunto coherente de principios compartidos en
una disciplina concreta. Básicamente, crea un estándar para todos. Tener distintos enfoques
es útil para resolver distintos tipos de problemas.
Hoy en día, existen numerosos paradigmas, pero la programación orientada a objetos es la
más popular, y es la que trataremos en este curso.
Según POO, el código debe cumplir los siguientes requisitos:

1. Está representado por un conjunto de objetos que interactúan entre sí y que


conforman una única estructura llamada programa. Los objetos almacenan datos y
tienen métodos que permiten pasar esos datos de un objeto a otro.
2. Se basa en los conceptos clave de POO, que son la encapsulación, la herencia y el
polimorfismo. Estudiaremos cada uno de ellos en detalle en este capítulo.

Pero primero, repasemos los fundamentos de POO y hablemos de cómo podemos trabajar
con objetos, sus métodos y sus clases.

Capítulo 3/10
Programación Orientada a Objetos

Objetos en POO
Vamos a explorar los objetos desde el punto de vista de la programación orientada a
objetos.
Podemos considerar casi cualquier cosa como un objeto. Por ejemplo, el dispositivo en el
que estás leyendo esta lección puede considerarse un objeto. Podemos describir algunas de
sus propiedades, como el color, el tamaño y el brillo de la pantalla. Del mismo modo,
también podemos describir una acción mediante una función. Por ejemplo, podemos
encender y apagar nuestros dispositivos o pulsar una tecla. A veces, las propiedades y los
métodos de los objetos de nuestro dispositivo están relacionados. Por ejemplo, si pulsamos
una tecla para ajustar el volumen, afectará a la propiedad "volumen".
Las funciones declaradas dentro de los objetos se denominan métodos. Un método, por lo
general, es cualquier función que se realiza sobre un objeto. También debemos señalar que
cualquier función en JavaScript es un método, incluso si se declara globalmente. En este
caso, es un método del objeto global. La función global alert() también puede ser llamada
como window.alert().
Los métodos son funciones, y ya hemos utilizado métodos muchas veces. Por ejemplo, ya
hemos conocido los métodos split() e indexOf() del tipo de datos string, y pronto exploraremos
formas de crear nuestros propios métodos.
Al igual que podemos pensar en nuestro dispositivo electrónico favorito como un objeto,
también podemos pensar en las cosas no físicas como objetos. Por ejemplo, podemos
describir una canción de la lista de reproducción que hicimos en el capítulo del DOM como
un objeto:
Copiar códigoJAVASCRIPT
const song = {
title: "Circles",
artist: "Mac Miller",
isLiked: false,
like: function () {
song.isLiked = !song.isLiked;
}
};
El objeto almacena datos en las propiedades title, artist, y isLiked. También tiene un
método like que le añade cierta funcionalidad. Los objetos sirven para encapsular datos y
funcionalidad en una sola entidad, presentándolos en un formato conveniente sobre el que
podemos construir.
En este contexto, los datos y la funcionalidad suelen denominarse "estado" y
"comportamiento". A continuación, entramos en detalle sobre estos conceptos:

1. El "estado" de un objeto incluye sus variables y propiedades, también llamadas


"campos". En el código anterior, los campos son song.title, song.artist, y song.isLiked.
2. El "comportamiento" de un objeto se define a través de funciones, llamadas
métodos del objeto. En el código anterior, song.like es un método del objeto song.

Esto resume las ideas claves de la programación orientada a objetos (POO). Se trata
esencialmente de combinar nuestros datos y la funcionalidad del programa dentro de los
objetos.

Sprint 8
Capítulo 3/10
Programación Orientada a Objetos

Datos y funcionalidad
Entremos un poco más en detalle sobre la utilización de objetos en POO.
En la lección anterior, explicamos que los objetos permiten almacenar datos y programar la
funcionalidad en un solo lugar.
Volvamos al ejemplo de la lista de reproducción para aplicar dicho concepto:
Nuestra genial lista de reproducción

Nuestro objeto song fue creado con una estructura estricta de atributos, es decir, propiedades
y métodos. Si queremos crear otro objeto song, tendrá que tener una estructura similar:
Copiar códigoJAVASCRIPT
const song = {
title: "Diary of Jane",
artist: "Breaking Benjamin",
isLiked: false,
like: function () {
song.isLiked = !song.isLiked;
}
}
El objeto song tiene las propiedades title, artist y isLiked, que podemos considerar como los
datos, mientras que el método like() representa la funcionalidad del objeto. Este
objeto song proporciona una forma de agrupar los datos y la funcionalidad de una canción
en una sola entidad.
Pero, naturalmente, una lista de reproducción suele tener varias canciones, así que vamos a
crear un objeto para cada una de ellas:
Copiar códigoJAVASCRIPT
const song1 = {
title: "Chanel",
artist: "Frank Ocean",
isLiked: false,
like: function () {
song1.isLiked = !song1.isLiked;
}
};

const song2 = {
title: "Circles",
artist: "Mac Miller",
isLiked: false,
like: function () {
song2.isLiked = !song2.isLiked;
}
};

const song3 = {
title: "Until I Walk Through The Flames",
artist: "Wicca Phase Springs Eternal",
isLiked: true,
like: function () {
song3.isLiked = !song3.isLiked;
}
};

Hemos creado tres objetos manualmente. Muy fácil, ¿verdad? Pero imagina que trabajas
con una lista de reproducción de 1.000 canciones. Para ello habría que crear 1.000 objetos.
Ya sabes que las funciones nos permiten evitar este tipo de tareas repetitivas. Escribamos
una función createSong() para crear nuevos objetos song:
Copiar códigoJAVASCRIPT
/* declara la función createSong,
que devolverá nuevos objetos song */
function createSong(title, artist) {
// crea un objeto llamado newSong
const newSong = {
title,
artist,
isLiked: false,
like: function () {
newSong.isLiked = !newSong.isLiked;
}
}

return newSong; // haz "return" en este objeto


}

// ahora es mucho más fácil crear un objeto song


const song1 = createSong("Chanel", "Frank Ocean");
const song2 = createSong("Circles", "Mac Miller");
const song3 = createSong("Until I Walk Through The Flames", "Wicca Phase Springs Eternal");

// prueba cómo funcionan los datos y la funcionalidad conjuntamente/


console.log(song1.isLiked); // false
song1.like();
console.log(song1.isLiked); // true
Cada vez que llamamos a createSong(), se devuelve un nuevo objeto song. Y como se trata de
objetos distintos, todas nuestras canciones son independientes unas de otras.
Aunque se trata de una forma más cómoda de crear canciones, sigue habiendo un problema
con este enfoque. Ahora mismo, cada objeto contiene su propia función like(). Si decidimos
añadir alguna funcionalidad a las canciones más adelante, como un
método dislike() o remove(), no existirá dentro de los objetos de las canciones que estamos
creando ahora.
Otro inconveniente importante es el consumo de memoria. Actualmente, cada objeto
contiene su propia función, y todas ellas ocupan una determinada cantidad de RAM.
Esencialmente, cada objeto que creemos requerirá una celda de almacenamiento separada
solo para su función. Pero como estas funciones realizan operaciones muy similares, no es
especialmente práctico crear tantas.
Afortunadamente, esto puede hacerse de otra manera. En la próxima lección, te
explicaremos cómo almacenar la funcionalidad común en un solo lugar, para que dentro de
nuestros objetos song solo almacenemos las características que pertenecen a cada canción.
Sprint 8
Capítulo 3/10
Programación Orientada a Objetos

Un rápido vistazo a "this"


Mediante el vídeo a continuación introduciremos "this", algo que es esencial para dominar
JavaScript.
En la lección anterior, creamos varios objetos "song", cada uno de los cuales contiene
instancias separadas del método like(). Es importante reiterar que cada instancia de este
método ocupa, de forma independiente, su propio espacio en la
memoria. song1.like() y song2.like(), ya que, como podemos comprobar si realizamos una
comparación, son dos métodos distintos:
Copiar códigoJAVASCRIPT
song1.like === song2.like // false
En el ejemplo anterior, habrás notado que no escribimos nuestros métodos con paréntesis ().
Esto es porque en realidad no llamamos a nada; solo queríamos compararlos.
Dentro de cada objeto "song", cada método like() realiza una acción similar. Simplemente
invierte el estado de isLiked para que sea el valor opuesto al que tuviera anteriormente.
Como te puedes imaginar, repetir estas funciones tan similares tantas veces en nuestro
código consume, de forma innecesaria, los recursos del sistema en los dispositivos de los
usuarios. Tiene mucho más sentido crear una función like() que nos permita cambiar las
propiedades de cualquier objeto que sean llamadas como método.
En primer lugar, vamos a declarar like() fuera del contexto de la función createSong(), luego la
pasaremos a nuestros nuevos objetos por su referencia:
Copiar códigoJAVASCRIPT
function like() {

function createSong(title, artist) {


const newSong = {
title,
artist,
isLiked: false,
like: like
}
return newSong;
}
Ahora, en la propiedad like del objeto newSong, todos los objetos tendrán un enlace a la
misma función independiente que pueden llamar como método:
Copiar códigoJAVASCRIPT
song1.like === song2.like // true
Deliberadamente, hemos eliminado el código del cuerpo de nuestra nueva función like().
Antes, cada uno de nuestros métodos like() hacía referencia a su propio objeto newSong,
volteando el valor de isLiked. La pregunta es: ¿cómo podemos hacer que nuestra función
independiente like() sea universal? Es decir, ¿cómo podemos hacer que funcione con cada
nuevo objeto que será creado por nuestra función createSong?
¡Este es un problema del que this puede ocuparse!
La propiedad this es una palabra clave disponible para cualquier función. Dependiendo de
cómo se llame a la función, el valor de this puede variar. En el próximo sprint, exploraremos
cinco posibles opciones, pero por ahora empecemos por ver el siguiente ejemplo:
Copiar códigoJAVASCRIPT
function like() {
this.isLiked = !this.isLiked;
}

function createSong(title, artist) {


const newSong = {
title,
artist,
isLiked: false,
like: like
}

return newSong;
}

const song1 = createSong("Chanel", "Frank Ocean");

song1.like(); // llamaremos a la función like() en song1

console.log(song1.isLiked); // true — ¡funcionó!


En este caso, el valor de this será el objeto sobre el que se ha llamado a la función. Es decir,
si se llama a una función como método de un objeto, la propiedad this almacena una
referencia al objeto sobre el que se ha llamado a esta función como método.
En nuestro ejemplo anterior, estamos llamando like() como un método del objeto song1, por
lo que el valor de this será una referencia a nuestro objeto, es decir, song1.
Podemos ver este cambio utilizando console.log() para mostrar el valor de this:
Copiar códigoJAVASCRIPT
const obj = {
prop: "Property",
method: function () {
console.log(this); // muestra el valor de this
}
}

/* { prop: "Property", method: ƒ }


este es el objeto sobre el que se ha llamado a dicho método */
obj.method();

Estamos llamando a method() sobre el objeto obj, por lo que obj se mostrará en la consola.
En el siguiente ejemplo, el método like() devolverá el valor de la propiedad isLiked dentro del
objeto song1:
Copiar códigoJAVASCRIPT
/* en este caso, dentro de la función like()
el valor de "this" será el objeto song1,
ya que estamos llamando al método like() en el objeto song */

song1.like();
El valor de this depende únicamente del objeto sobre el que se llama a la función, y no del
objeto en el que se almacena. En nuestro ejemplo, el valor de this es el objeto song1. Esto es
debido a que este es el objeto sobre el que se llama a la función como método.
Prueba a adivinar cuál será el valor de this en el siguiente ejemplo, dentro de las
funciones like():
Copiar códigoJAVASCRIPT
song2.like();
song3.like();
Una vez más, si la función se llama como un método de objeto, entonces en el momento de
la llamada, el valor de this dentro de esa función será el objeto sobre el que se llamó la
función.
Por lo tanto, la respuesta es:
Copiar códigoJAVASCRIPT
song2.like(); // en este caso, el valor de "this" será song2
song3.like(); // y en este, el valor de "this" será song3
Ahí lo tienes, tres lecciones de teoría POO repletas de acción. Empecemos a practicar.

Sprint 8
Capítulo 3/10
Programación Orientada a Objetos

Clases
Puedes seguir leyendo si lo prefieres, pero mediante el vídeo a continuación te
plasmamos una idea general sobre el tema que trataremos en esta lección, échale
un vistazo:
El código de inicio se encuentra en este replit (materiales en inglés).
Ahora ya sabes cómo crear objetos con propiedades únicas donde nuestra
funcionalidad se almacena en un solo lugar y está disponible para que cualquier
objeto pueda acceder a ella. Este enfoque nos permite reducir el uso de memoria
de la aplicación. En esta lección, nos centraremos en cómo conseguir el mismo
resultado utilizando clases.
La palabra clave class
Una clase define los datos y la funcionalidad que poseerá un objeto. Piensa en
ello como un plano utilizado para crear objetos. Estos objetos se denominan
instancias de clase. Las clases declaran tanto propiedades (datos) como métodos
(funcionalidad) en sus objetos.
Vamos a crear el objeto song que construimos anteriormente llamando a la
función createSong(), pero esta vez, vamos a utilizar la sintaxis de clase que se
introdujo en ES6 (ES2015).
ES6 es la sexta versión de la especificación ECMAScript introducida en 2015.
Por eso a veces también se le conoce como ES2015.
Para definir una clase, tendremos que escribir la palabra clave class, seguida del
nombre de una variable a la que podamos acceder para utilizar la clase. En
muchos lenguajes de programación, incluido JavaScript, los nombres de nuestras
clases deben comenzar con una letra mayúscula. Todo seguirá funcionando bien
si el nombre de tu clase empieza con una letra minúscula, pero es mejor seguir
esta convención de nomenclatura para que sea fácil determinar qué variables
contienen clases:
Copiar códigoJAVASCRIPT
class Song {
constructor(title, artist) {
this.title = title;
this.artist = artist;
}

like() {
this.isLiked = !this.isLiked;
}
}

const song = new Song("Start Over", "Any Given Day");


A primera vista, no parece que haya mucha diferencia entre la
clase createSong() creada en la lección anterior y la clase Song; pero, más adelante,
cuando exploremos la herencia (uno de los principales paradigmas de POO), las
diferencias se harán más notables

El método constructor()
Cada clase contiene el método constructor(), el cual es llamado cada vez que se
inicializa un nuevo objeto de una clase. El método constructor() rellena nuestro
futuro objeto con datos.
La expresión new Song("Start Over", "Any Given Day") devuelve un nuevo objeto. El
operador new se utiliza para iniciar una clase. El uso de esta palabra clave muestra
que la clase iniciada devolverá un nuevo objeto, que es una instancia de esta
clase. Si intentamos iniciar la clase sin new, se producirá un error, ya que el
método constructor() no puede ser llamado sin el operador new.
En el ejemplo anterior, la palabra clave this dentro del método constructor() actúa
como un enlace al objeto devuelto. Esto significa que el valor de nuestros
argumentos de entrada, title y artist, se almacenarán en las
propiedades this.title y this.artist del objeto que almacenamos en la variable song.
Podemos omitir la descripción del método constructor() dentro de la clase. En ese
caso, constructor() se creará implícitamente como un método con un cuerpo
vacío constructor() {}. En consecuencia, provoca que no se añadan datos
adicionales a los nuevos objetos.
Aparte del método constructor(), en el que se añaden propiedades a un nuevo
objeto, las clases pueden tener otros métodos. Todos los objetos creados con new
Song() podrán acceder a estos métodos:
Copiar códigoJAVASCRIPT
const song1 = new Song("Start Over", "Any Given Day");
const song2 = new Song("Bitter End", "The Veer Union");

song1.like === song2.like // true


Fíjate en que todos los objetos que sean creados con la clase Song compartirán el
método like() que hemos almacenado en la memoria como una única instancia.
Las clases nos ayudan a gestionar el uso de la memoria, tema que tratamos en las
lecciones anteriores.
¡Vamos allá!

Sprint 8
Capítulo 3/10
Programación Orientada a Objetos

Encapsulación
En el mundo actual, estamos constantemente rodeados de todo tipo de dispositivos, pero
esto no significa necesariamente que tengamos un conocimiento profundo de los mismos.
No necesitamos saber qué pasa cuando encendemos nuestro PC. Nos basta con saber que
para ello solo necesitamos pulsar el botón de encendido. En programación, este botón se
define como una parte de la interfaz de un PC.
El funcionamiento interno de un programa suele estar oculto al mundo exterior. A este
hecho lo llamamos encapsulación. La principal ventaja es que oculta la complejidad del
dispositivo, dejando al usuario una forma cómoda e intuitiva de interactuar con él.

Interfaz de usuario
En programación, a menudo nos encontramos con el término "interfaz de usuario". Cuando
hablamos de una interfaz de usuario, nos referimos a la apariencia del programa. Esto
incluye cosas como el menú de usuario, cualquier botón o interruptor, campos de entrada,
etc. Es lo que permite a nuestros usuarios interactuar con un programa, y actúa como medio
entre el código del programa y el usuario.
Todo dispositivo complejo debe tener una interfaz fácil de usar. Es mucho más sencillo
arrancar un coche con una llave que levantar el capó e intentar arrancar el motor
manualmente.

Principio de interfaz en POO


En POO se aplica este mismo principio en los objetos. Los objetos externos de la interfaz
son los métodos públicos que se pueden llamar con seguridad en el código externo.
Además de los métodos públicos, los objetos también tienen métodos y propiedades
privadas. La diferencia entre ambos es que los métodos privados están destinados
únicamente al funcionamiento interno de un objeto. Más tarde veremos un ejemplo para
hacernos una mejor idea.
Hasta hace poco, JavaScript no tenía realmente métodos y propiedades privadas. Por ello,
los desarrolladores web debían marcar los métodos y propiedades como privados o emular
esta funcionalidad.
Hablemos primero de las nuevas características.

Métodos y propiedades privadas reales


Como íbamos diciendo, ahora realmente podemos crear métodos y clases privadas reales
usando el signo de almohadilla # en su nombre. Tradicionalmente, los desarrolladores han
utilizado el guion bajo (_) para dar a otros desarrolladores una señal visual de que su código
es privado (a veces se les llama propiedades protegidas). El signo de almohadilla (#) en
realidad los hace privados en el código:
Copiar códigoJAVASCRIPT
class Car {
#gasTankValue
#maxGasTankValue
#fuelConsumption
constructor(maxGasTankValue, fuelConsumption) {
this.#gasTankValue = 0;
this.#maxGasTankValue = maxGasTankValue;
this.#fuelConsumption = fuelConsumption; //millas por galón
}

#getAvailableGasValue(gasValue) {
if (gasValue < 0) return 0;
if (gasValue > this.#maxGasTankValue) return this.#maxGasTankValue;
return gasValue;
}

refuel(gasValue) {
this.#gasTankValue = this.#getAvailableGasValue(gasValue);
}

getDistance() {
return this.#gasTankValue / this.#fuelConsumption * 100;
}
}

const car = new Car(70, 9);


// El siguiente código dará lugar a un error. No hay acceso a las propiedades privadas desde el
exterior.
car.#gasTankValue = -10
Sin embargo, muchos navegadores no soportan esta nueva sintaxis con #. Para que el
código funcione correctamente, tendremos que utilizar herramientas especiales para
convertir el código escrito con la nueva sintaxis en código escrito de forma estándar.
Discutiremos el nuevo enfoque de crear métodos y propiedades privadas un poco más
adelante. Por ahora, vamos a utilizar el guion bajo para indicar cuales son nuestros métodos
y propiedades privadas. Ahora explicaremos cómo hacerlo.
La clase Car tiene dos métodos públicos. El método refuel() establece la cantidad de gasolina
en el deposito. El segundo método devuelve la distancia que puede recorrer un coche con la
cantidad de gasolina especificada.

Emulación de métodos y propiedades privadas


Los desarrolladores web tienen una regla tácita de comenzar los métodos y propiedades
privadas con un guión bajo. De esta manera, otros desarrolladores web pueden ver qué
métodos y propiedades son privados y pueden evitar utilizarlos directamente en el código
externo:
Copiar códigoJAVASCRIPT
class Car {
constructor(maxGasTankValue, fuelConsumption) {
this._gasTankValue = 0;
this._maxGasTankValue = maxGasTankValue;
this._fuelConsumption = fuelConsumption; // millas por galón
}

_getAvailableGasValue(gasValue) {
if (gasValue < 0) return 0;
if (gasValue > this._maxGasTankValue) return this._maxGasTankValue;
return gasValue;
}

refuel(gasValue) {
this._gasTankValue = this._getAvailableGasValue(gasValue);
}

getDistance() {
return this._gasTankValue / this._fuelConsumption * 100;
}
}

const car = new Car(70, 9);


car.refuel(45);

console.log(car._gasTankValue); // 45. La propiedad no es realmente privada y es fácil cambiarla.


console.log(car.getDistance()); // 500
En este ejemplo, nada nos impide simplemente establecer una cantidad negativa de gasolina
como en la expresión car._gasTankValue = -1. Pero esta interferencia externa en el código
interno interrumpiría el trabajo del objeto y provocaría un error.
Por eso, cuando se trabaja con instancias de clases, es mejor evitar usar o acceder a
métodos y propiedades privadas. Este enfoque te ayudará a evitar que se produzcan errores
en tu código.
El siguiente código inicializa una instancia de la clase Car:
Copiar códigoJAVASCRIPT
const car = new Car(54, 5.5)
Podemos llamar al método car.refuel(100) para llenar el depósito y al método getDistance() para
devolver el número de kilómetros que puede recorrer el coche. Observa que el
método refuel() almacena un método privado llamado _getAvailableGasValue(). Este método
privado evita que nadie pueda agregar una cantidad negativa de gas o un número mayor que
el volumen del deposito a la propiedad privada _gasTankValue. El volumen del depósito es el
primer argumento que pasaremos al llamar al método new Car().
Siempre que utilizamos un método privado, llamamos al método y leemos y registramos las
propiedades privadas al mismo tiempo. Pero, en realidad, no necesitamos conocer esta
información adicional. En este caso, el uso de propiedades y métodos privados se encapsula
dentro de una clase. Encapsular significa ocultar ciertos datos o funcionalidades al mundo
exterior.
En resumen, la encapsulación nos ayudó a hacer un objeto fácil de usar que consta de 3
métodos y 3 propiedades, y también dejamos dos métodos públicos para que sirvan de
interfaz del objeto.

Sprint 8
Capítulo 3/10
Programación Orientada a Objetos

Herencia
En la programación orientada a objetos, la herencia significa crear una clase basada en otra
clase.
En nuestra vida, todos hemos podido observar cómo funciona el concepto de herencia.
transmisión de rasgos de padres a hijos.
Estos mismos principios también tienen cabida en el mundo de la programación. Ya lo has
visto en acción. Cuando estudiamos HTML y CSS, viste que los estilos aplicados a un
elemento padre son heredados por sus elementos hijos. En JavaScript, la herencia permite a
los programadores reutilizar clases escritas previamente. Podemos tener clases hijo o clases
padre, también denominadas subclases y superclases respectivamente. Esto les ayuda a
eliminar la excesiva duplicación de código y hace que sea mucho más fácil de mantener.
Veamos el siguiente ejemplo para entenderlo mejor:
Copiar códigoJAVASCRIPT
class Student {
constructor(name, group) {
this._name = name;
this._group = group;
this._profession = null;
this._trainingDuration = null;
}

getInfo() {
return {
name: this._name,
group: this._group,
profession: this._profession,
trainingDuration: this._trainingDuration
}
}
}
Aquí tenemos la clase Student, que crea un objeto que almacena información sobre un
estudiante. La clase toma dos argumentos en la inicialización; el nombre del estudiante, y el
número de su grupo. Las propiedades sobre la profesión (this._profession) y la duración de la
formación (this._trainingDuration) reciben el valor null.
Si aplicamos las reglas de lectura y escritura de valores en propiedades privadas de la
lección anterior, no deberíamos cambiar las propiedades this._profession y this._trainingDuration.
Pero ahora mismo, un objeto de la clase Student no tiene las interfaces públicas necesarias
para cambiar estas propiedades privadas.
De hecho, no necesitamos estas interfaces públicas. Por supuesto, podríamos añadir más
parámetros a la clase Student, y luego asignar estos valores
a this._profession y this._trainingDuration, de la siguiente manera:
Copiar códigoJAVASCRIPT
class Student {
constructor(name, group, profession, trainingDuration) {
this._name = name;
this._group = group;
this._profession = profession;
this._trainingDuration = trainingDuration;
}
}
Sin embargo, es mejor que intentemos pasar el mínimo número de argumentos posible
cuando estemos creando instancias de clase.
A veces, queremos que un valor permanezca igual, independientemente de los valores que
el usuario pase al método constructor. Se denominan valores estáticos. Entonces, ¿cómo
podemos crear objetos con propiedades privadas que contengan valores estáticos sin utilizar
los parámetros de constructor()? Una opción sería crear una clase separada para cada tipo de
objeto:
Copiar códigoJAVASCRIPT
// clase para estudiantes del curso de Desarrollador Web
class WebDeveloperStudent {
constructor(name, group) {
this._name = name;
this._group = group;
this._profession = "Web Developer";
this._trainingDuration = 10;
}

getInfo() {
return {
name: this._name,
group: this._group,
profession: this._profession,
trainingDuration: this._trainingDuration
}
}
}

// clase para los alumnos del curso de Desarrollador de Python


class PythonDeveloperStudent {
constructor(name, group) {
this._name = name;
this._group = group;
this._profession = "Python Developer";
this._trainingDuration = 9;
}

getInfo() {
return {
name: this._name,
group: this._group,
profession: this._profession,
trainingDuration: this._trainingDuration
}
}
}
// clase para estudiantes del curso de Analista de Datos
class DataAnalystStudent {
constructor(name, group) {
this._name = name;
this._group = group;
this._profession = "Data Analyst";
this._trainingDuration = 6;
}

getInfo() {
return {
name: this._name,
group: this._group,
profession: this._profession,
trainingDuration: this._trainingDuration
}
}
}
Ahora tenemos 3 clases separadas que no están vinculadas a la antigua clase Student de
ninguna manera. La desventaja de este código es que tenemos que escribir todas las
propiedades que consideramos repetitivas y el método getInfo() en cada clase. Las líneas que
definen los valores de this._name y this._group son también las mismas en las tres clases.
Además, si necesitamos modificar el método getInfo(), tendremos que hacerlo tres veces, una
para cada clase.
Podemos resolver el problema de la duplicación de código en nuestras clases mediante la
herencia. Podemos utilizar la palabra clave extends para crear nuevas clases que heredarán
todas las propiedades y métodos de sus clases padre:
Copiar códigoJAVASCRIPT
class WebDeveloperStudent extends Student {

class PythonDeveloperStudent extends Student {

class DataAnalystStudent extends Student {

}
Ahora hemos creado tres clases de estudiantes en función de la profesión a la que se
quieren dedicar. Las tres clases heredan de la misma clase padre Student porque hemos
utilizado la palabra clave extends.
Según la tarea inicial, las clases deben tener sus propios valores únicos para las
propiedades _profession y _trainingDuration. Esto significa que tenemos que llamar al
método constructor() en cada clase y asignar los valores correctos para las
propiedades _profession y _trainingDuration.
super() es llamado en cada clase de constructor heredada de la clase Student. La palabra
clave super nos permite hacer una referencia a la clase padre de la que hereda la clase hijo.
Escribir super() llama al método constructor() de la clase padre. En este caso, se trata de la
clase Student.
Copiar códigoJAVASCRIPT
class WebDeveloperStudent extends Student {
constructor(name, group) {
super(name, group);
this._profession = "Desarrollador web";
this._trainingDuration = 10;
}
}

class PythonDeveloperStudent extends Student {


constructor(name, group) {
super(name, group);
this._profession = "Desarrollador de Python";
this._trainingDuration = 9;
}
}

class DataAnalystStudent extends Student {


constructor(name, group) {
super(name, group);
this._profession = "Analista de datos";
this._trainingDuration = 6;
}
}
Algo que cabe mencionar cuando utilizamos la palabra clave extends es que siempre que
necesites definir el método constructor() dentro de una clase hijo, debes llamar a super() dentro
de ella y pasarle las propiedades necesarias como se muestra en el ejemplo anterior. Si no
llamas a super(), obtendrás un error que provocará la terminación del script.
El uso de las palabras clave super() y extends nos permitió trabajar con las
propiedades this._name y this._group de nuestra clase padre y evitar duplicarlas en el
constructor de cada clase hijo.
Cada clase hijo redefine las propiedades this._profession y this._trainingDuration de su clase padre.
Por lo tanto, llamar a getInfo() de instancias de diferentes clases devolverá los valores
correctos de this._profession y this._trainingDuration dependiendo de la clase:
Copiar códigoJAVASCRIPT
const student1 = new WebDeveloperStudent("Wendy Webberton", 1);
const student2 = new DataAnalystStudent("Dennis Databerg", 3);

student1.getInfo();

/*
{
name: "Wendy Webberton",
group: 1,
profession: "Web developer",
traningDuration: 10
}
*/

student2.getInfo();

/*
{
name: "Dennis Databerg",
group: 3,
profession: "Data analyst",
traningDuration: 6
}
*/
Puede ser un poco difícil absorber toda esta información simplemente leyendo. Pero no te
preocupes, con algo de práctica se verá todo más claro.

Sprint 8
Capítulo 3/10
Programación Orientada a Objetos
Polimorfismo
En la lección anterior, tratamos el tema de la herencia en JavaScript y estudiamos un
ejemplo en el cual las clases hijo heredaban el método getInfo() de la clase de sus padres.
Este mecanismo hace que trabajar con clases sea mucho más conveniente. Podemos
declarar un método una sola vez y luego usarlo con cada instancia de nuestras clases hijas.
Echémosle otro vistazo:
Copiar códigoJAVASCRIPT
class Student {
constructor(name, group) {
this._name = name;
this._group = group;
this._profession = null;
this._trainingDuration = null;
}

getInfo() {
return {
name: this._name,
group: this._group,
profession: this._profession,
trainingDuration: this._trainingDuration
}
}
}

class WebDeveloperStudent extends Student {


constructor(name, group) {
super(name, group);
this._profession = "Desarrollador web";
this._trainingDuration = 10;
}
}

class PythonDeveloperStudent extends Student {


constructor(name, group) {
super(name, group);
this._profession = "Desarrollador de Python";
this._trainingDuration = 9;
}
}

class DesignerStudent extends Student {


constructor(name, group) {
super(name, group);
this._profession = "Diseñador";
this._trainingDuration = 6;
}
}
Hemos creado tres clases de alumnos que estudian en diferentes campos. Las tres clases
serán herederas de la misma clase padre Student.
Hay que tener en cuenta que no describimos el método getInfo() ni las
propiedades this._name y this._group en ninguna de las clases hijas. Estas tres clases heredan
estos métodos de la clase padre porque llamamos super() en sus constructores.
Copiar códigoJAVASCRIPT
const student1 = new WebDeveloperStudent("Wendy Webberton", 1);
const student2 = new PythonDeveloperStudent("Peter Pythonson", 2);
const student3 = new DesignerStudent("Debbie Designerperson", 3);
Vamos a definir una función que tomará un array como argumento. Este array contendrá
aspectos de nuestros estudiantes recién creados y devolverá un array de datos sobre ellos:
Copiar códigoJAVASCRIPT
function getInfoList(students) {
if (!Array.isArray(students)) return [];
return students.map(student => student.getInfo());
}

getInfoList([student1, student2, student3]);


Supongamos que tenemos la tarea de añadir una propiedad language a los objetos que
almacenan los datos del alumno, esta propiedad contendrá el valor del lenguaje de
programación estudiado por el alumno durante el curso. Para un desarrollador web será
JavaScript, Python para un desarrollador de Python, y los futuros diseñadores no tendrán un
lenguaje de programación en este campo.
Como los diseñadores son una categoría algo diferente, la implementación del
método getInfo() de la clase DesignerStudent debe ser distinta a la del método getInfo() de las
clases WebDeveloperStudent y PythonDeveloperStudent.
Podemos hacer frente a esto mediante el uso de la anulación de métodos, que es una
característica del lenguaje que permite a una clase hija marcar de forma específica como
quiere implementar un método que fue heredado de su clase padre.
Vamos a reescribir la clase WebDeveloperStudent:
Copiar códigoJAVASCRIPT
class WebDeveloperStudent extends Student {
constructor(name, group) {
super(name, group);
this._profession = "Web Developer";
this._trainingDuration = 10;
}

getInfo() {
return {
name: this._name,
group: this._group,
profession: this._profession,
trainingDuration: this._trainingDuration,
language: "Javascript"
}
}
}
El método getInfo() de la subclase WebDeveloperStudent es ahora diferente del método getInfo() de
la clase padre Student.
Esto significa que cuando llamemos al método getInfo() para la clase WebDeveloperStudent, se
invocará la función que escribimos para nuestra subclase desarrollador web en lugar del
método getInfo() de la superclase Student.
Copiar códigoJAVASCRIPT
const studentWithoutProfession = new Student("Mike Realperson", 10);
const student1 = new WebDeveloperStudent("Wendy Webberton", 11);
const student2 = new WebDeveloperStudent("Mary Webster", 6);

console.log(student1.getInfo === student2.getInfo) // true; las entidades de la clase


WebDeveloperStudent comparten el mismo método.
console.log(studentWithoutProfession.getInfo === student1.getInfo) // false; son funciones diferentes.
Hagamos lo mismo con la clase PythonDeveloperStudent:
Copiar códigoJAVASCRIPT
class PythonDeveloperStudent extends Student {
constructor(name, group) {
super(name, group);
this._profession = "Python Developer";
this._trainingDuration = 10;
}

getInfo() {
return {
name: this._name,
group: this._group,
profession: this._profession,
trainingDuration: this._trainingDuration,
language: "Python"
}
}
}
Si programamos nuestras clases WebDeveloperStudent y PythonDeveloperStudent de esta manera,
esto nos permitirá utilizar con seguridad nuestra función getInfoList() para poder obtener
información sobre nuestros estudiantes. Además, la instancia de la
clase DesignerStudent seguirá utilizando el método getInfo() de la clase Student:
Copiar códigoJAVASCRIPT
getInfoList([student1, student2, student3]);

Polimorfismo
Acabas de ver el concepto de polimorfismo en acción, este se basa en la capacidad de hacer
que objetos con la misma interfaz tengan una implementación diferente.
La función getInfoList() llama al método getInfo() de las instancias pasadas en secuencia, y la
implementación de dicho método dependerá de la clase de la instancia.
Puede que te hayas dado cuenta de que anular un método en nuestro ejemplo ha llevado a
una excesiva duplicación de código. La parte principal del código relativo al
método getInfo() se sigue repitiendo en cada clase hija. Esto se puede arreglar fácilmente. No
solo podemos anular el método de la clase padre, también podemos extenderlo.
Si utilizamos la palabra clave super en el método de la clase hija, podemos seguir utilizando
la funcionalidad de la clase padre.
Una vez que anulamos el método getInfo() en la clase hija, escribir this.getInfo() será igual a
llamar al método de la clase hija. Pero aún podemos llamar a la función getInfo() de la clase
padre. Podemos hacerlo llamando a super.getInfo() en lugar de this.getInfo().
En el siguiente ejemplo, super.getInfo() llama al método getInfo() de la clase Student:
Copiar códigoJAVASCRIPT
class WebDeveloperStudent extends Student {
constructor(name, group) {
super(name, group);
this._profession = "Web Developer";
this._trainingDuration = 10;
}

getInfo() {
return super.getInfo();
}
}
es un método de la clase padre Student, que devuelve un objeto con información
super.getInfo()
del estudiante.
De hecho, el código anterior funciona de la misma manera que lo haría sin anular ningún
método. En el ejemplo anterior, podrías eliminar sin problemas la implementación del
método getInfo() de la clase WebDeveloperStudent, y nada cambiaría. La
clase WebDeveloperStudent simplemente heredará el método de la clase padre.
Copiar códigoJAVASCRIPT
class WebDeveloperStudent extends Student {
constructor(name, group) {
super(name, group);
this._profession = "Desarrollador web";
this._trainingDuration = 10;
}
}

const studentWithoutProfession = new Student("Mike Realperson", 1);


const student1 = new WebDeveloperStudent("Wendy Webberton", 11);

//student1 utiliza el método getInfo() de la clase padre Student


console.log(studentWithoutProfession.getInfo === student1.getInfo) // true
Sin embargo, esto no es lo que necesitamos. Como hemos mencionado antes, queremos
añadir información sobre el lenguaje de programación que el alumno está estudiando
mediante el método getInfo() de ciertas subclases, pero queremos hacerlo sin duplicar el
código de la superclase.
En realidad, esto es bastante fácil de hacer:
Copiar códigoJAVASCRIPT
class WebDeveloperStudent extends Student {
constructor(name, group) {
// ...
}

getInfo() {
const info = super.getInfo();
info.language = "Javascript";
return info;
}
}
Llamar a super.getInfo() devuelve el resultado de getInfo() de la clase padre Student, que en este
caso, es un objeto. A continuación, asignamos ese objeto a la variable info, y después
añadimos la propiedad language a ese objeto. Finalmente, el método getInfo() de la clase
hija WebDeveloperStudent devuelve el objeto info actualizado. Así es como podemos ampliar
nuestros métodos, ¡es eficiente y se ve genial!
Sprint 8
Capítulo 3/10
Programación Orientada a Objetos

Programación orientada a objetos:


Conclusión
¡Enhorabuena! Has escrito tu primer código utilizando la programación orientada a objetos.
POO no es el único paradigma de programación que existe, pero en este momento es el más
destacado con diferencia. Es muy probable que te encuentres con muchos programas
escritos con POO mientras trabajes como programador.
Ahora ya tienes un manejo básico de la encapsulación, la herencia y el polimorfismo. Sin
embargo, todavía no hemos aclarado el cómo implementar todos estos conceptos en tu
código para crear interfaces que te sean útiles en tu día a día. Si tienes ganas de saber cómo,
¡vayamos al siguiente capítulo!
Sprint 8
Capítulo 4/10
Interfaces en POO — Parte 1

Introducción a las interfaces en la


programación orientada a objetos (POO)
Nabila ha terminado de leer el manual y ahora está preparada para aplicar la teoría que ha
aprendido. La base de su "Centro Comunitario Nano" ya está lista, y dentro de poco la
interfaz de su maqueta será tan buena (o incluso mejor) que la creación de su tío. Ahora te
toca a ti poner en práctica la teoría, es decir, aplicar los principios de POO que has
aprendido.
En el capítulo anterior, estábamos trabajando con un ejemplo bastante sencillo. Teníamos la
clase Student y creamos clases hijas especializadas para los alumnos que estudian para ser
desarrolladores web y analistas de datos. Las
subclases WebDeveloperStudent y DataAnalystStudent han sido heredadas de la clase padre Student.
Pero, ¿cómo podemos implementar este tipo de herencia en una interfaz de la vida real?
Antes de empezar a hacerlo, tenemos que resolver varias cuestiones.
La primera es: ¿qué almacena exactamente el constructor?¿Qué partes del marcado pueden
representarse como una clase y cuáles no? ¿Y por qué razón tenemos que optar por las
clases cuando podemos trabajar con funciones?
Entender los principios de la abstracción nos ayudará a responder a estas preguntas. En la
programación orientada a objetos, se entiende por abstracción el proceso de ocultar la
complejidad de un sistema y mostrar únicamente su funcionalidad. Trabajar con interfaces
en la programación orientada a objetos consiste en lograr un equilibrio entre los conceptos
abstractos y la realidad. Aprenderás a manipular un árbol DOM creando clases y
gestionando la herencia de sus métodos y propiedades.
En cualquier caso, a menudo tendrás que responder a estas preguntas:

 ¿Cuál será el papel y el propósito de la clase?


 ¿Qué datos se almacenarán en la clase?
 ¿Qué métodos debería tener la clase?
 ¿Se puede acceder a algunos métodos desde fuera, y si es así, a cuáles?
 ¿Cómo implementaremos la herencia?
A medida que vayamos respondiendo a estas preguntas, empezaremos a ver con claridad
por qué la programación orientada a objetos es tan útil. Es realmente fácil añadir nuevas
funcionalidades a las clases.
En este capítulo, hablaremos de cómo están conectados el DOM y la POO. Además,
trabajarás con algunos ejercicios en los que podrás aplicar tus nuevos conocimientos acerca
de la herencia y el polimorfismo. ¿Estás preparado para enfrentarte a las interfaces?
¡Vamos allá!

Sprint 8
Capítulo 4/10
Interfaces en POO — Parte 1

¿Cómo se trabaja con una plantilla de


marcado dentro de una clase?
Cuando los usuarios visitan un sitio web, pueden ver varios elementos de la página tales
como bloques de texto, imágenes, botones, etc. Los elementos de la página pueden ser
estáticos o dinámicos. Veamos ambos con más detalle.
Los elementos estáticos mantienen las mismas condiciones en varias páginas. Una vez
cargada la página web, el contenido estático no cambia. Por ejemplo, el logotipo o el pie de
página de un sitio web suelen ser los mismos. Todos los elementos estáticos se almacenan
en un archivo HTML.
Los elementos dinámicos, en cambio, pueden cambiar sin que la página se actualice. Este
comportamiento suele depender de las acciones del usuario. Por ejemplo, así es como
funcionan los filtros de productos en las tiendas online. Clasifican los productos por tipo y
luego muestran una lista filtrada que contiene los productos deseados. Los elementos
dinámicos son creados por scripts en el archivo index.js.
Cuando hay varios elementos dinámicos similares en una página, es mejor escribir nuestros
scripts utilizando la POO. Esto facilita de forma considerable la gestión de nuestro código.
Su funcionamiento se basa en que los desarrolladores web (¡es decir, nosotros!) creamos
una clase que contiene el marcado de sus elementos dinámicos. Esta clase sirve entonces
como fábrica para producir bloques de código del mismo tipo. De esta manera, si alguna
vez necesitamos cambiar las propiedades o el comportamiento de nuestros bloques, bastará
con editar una clase.

Demostración de la POO a través de un ejemplo de chat


Imaginemos que tu director de proyecto te ha enviado una tarea interesante. Necesitamos
crear un chat para el equipo de soporte de TripleTen con el fin de poder comunicarse con
los estudiantes. Echemos un vistazo a una tarjeta de mensajes básica:
Ejemplo de mensaje de tarjeta
Los mensajes pueden cambiar cada vez que el usuario interactúe con ellos. Los usuarios
pueden escribir nuevos mensajes y manipular los antiguos borrándolos o marcándolos
como favoritos. Evidentemente, los mensajes son elementos dinámicos y por eso
necesitamos utilizar JavaScript para renderizarlos.
Los mensajes del chat también tienen una estructura repetitiva. Cada mensaje contiene algo
de texto y posiblemente algunas imágenes. Lo único que realmente cambia es el contenido
de los mensajes. Por supuesto, estas son características básicas, que se podrían cambiar o
ampliar con el tiempo.
La POO será perfecta para nuestro chat. Vamos a crear una fábrica para nuestras tarjetas de
mensajes. Es decir, crear una clase que almacene el marcado de la tarjeta y que añada
contenido único a nuestros mensajes.
Vamos a agregar la clase a index.js:
Copiar códigoJAVASCRIPT
class Card {
// el constructor almacenará datos dinámicos,
// cada instancia tendrá sus propios datos personales
constructor(text, image) {
// el texto y la imagen son campos privados,
// son necesarios únicamente dentro de la clase
this._text = text;
this._image = image;
}
}

// Vamos a crear una instancia de una tarjeta con un texto único y una imagen de usuario
const card = new Card("¡Hola! ¿Cómo estás?", "userpic.jpg");
Ten en cuenta que nuestro constructor se va a volver mucho más complejo a lo largo del
proceso de desarrollo. Por ahora, nuestra clase solo contiene datos, que deben ser únicos
para cada mensaje en el chat.
Plantilla de marcado
Nuestra siguiente tarea es hacer que la clase Card devuelva nuestro marcado. Tuviste la
misma tarea en el proyecto "Alrededor de los Estados Unidos" donde tu función tenía que
devolver un elemento del DOM.
En la POO, esta función se convertirá en un método de la clase. Llamemos a nuestro
método _getTemplate():
Copiar códigoJAVASCRIPT
class Card {
constructor(text, image) {
this._text = text;
this._image = image;
}

_getTemplate() {
// realiza todas las acciones necesarias para obtener el marcado aquí
}
}

const card = new Card("¡Hola! ¿Cómo estás?", "userpic.jpg");


¿Ves el guión bajo? Nuestro método _getTemplate() es privado. Lo llamaremos dentro de la
clase para obtener el marcado necesario antes de colocarlo en la página. De este modo,
diferenciamos el proceso de creación del marcado, de su visualización en la página. Dicha
diferenciación de funciones en nuestro código debe ser siempre lo más clara posible, para
controlar nuestros scripts de forma eficiente.
Por la misma razón, también separamos el método de creación de tarjetas del marcado.
Vamos a poner el marcado dentro del elemento <template> y a colocarlo en index.html:
Copiar códigoHTML
<template class="card-template">
<div class="card">
<img src="" alt="Userpic" class="card__avatar">
<div class="card__text">
<p class="card__paragraph"><!--el texto del mensaje aparecerá aquí --></p>
</div>
</div>
</template>
De esta manera, haremos que el código de getTemplate() sea más sencillo. En lugar de escribir
todo el marcado dentro del propio método, aprovecharemos el marcado del
elemento <template>. Si necesitas refrescar este tema, ya nos hemos enfrentado a una tarea
similar en el capítulo que se refería a los elementos de plantilla:
Copiar códigoJAVASCRIPT
_getTemplate() {
// toma el marcado de HTML y copia el elemento
const cardElement = document
.querySelector(".card-template")
.content
.querySelector(".card")
.cloneNode(true);

// devuelve el elemento DOM de la tarjeta


return cardElement;
}
Todo lo que queda es agregar el contenido del mensaje al marcado, lo cual será uno de los
ejercicios de esta lección.

Sprint 8
Capítulo 4/10
Interfaces en POO — Parte 1

Añadir datos al marcado e insertarlos en el


DOM
En la lección anterior, hemos configurado nuestra clase de JavaScript de modo que pudiera
trabajar con datos dinámicos y la hemos conectado a nuestro código. Ahora vamos a
averiguar cómo podemos añadir texto e imágenes a nuestro marcado.
Podemos aprender a hacerlo volviendo a nuestro ejemplo del chat.

¡Un mensaje de chat sobre un chat!


El código HTML es el siguiente:
Copiar códigoHTML
<template class="card-template">
<div class="card">
<img src="" alt="Userpic" class="card__avatar">
<div class="card__text">
<p class="card__paragraph"><!--el texto del mensaje aparecerá aquí --></p>
</div>
</div>
</template>
Pero, ¿cómo podemos realmente poblar nuestros elementos HTML con mensajes? Primero,
en el elemento con la clase card__avatar, estableceremos el valor del atributo src para que sea
igual a la URL de nuestra imagen. A continuación, colocaremos el texto de nuestro mensaje
en el bloque de código con la clase card__paragraph. De este modo, cada nueva instancia de la
clase Card tendrá su propio contenido.
En index.js, definiremos un array de objetos, donde cada uno de ellos contendrá la imagen y
el texto del usuario:
Copiar códigoJAVASCRIPT
const messageList = [
{
image:
"https://practicum-content.s3.us-west-1.amazonaws.com/web-code/moved_card__image.jpg",
text: "Hola, ¡tenemos que configurar nuestro chat lo antes posible!"
}
{
image: "https://practicum-content.s3.us-west-1.amazonaws.com/web-code/moved_card__image-
lake.jpg",
text: "¡Ahora podemos crear tantas tarjetas como necesitemos!"
},
];

Preparación de una tarjeta para la visualización


Sin embargo, antes de mostrar la tarjeta en la página, tenemos que llenarla de datos.
También tenemos que definir su comportamiento, pero lo dejaremos para después.
Crearemos un método independiente llamado generateCard(). Este es un método público
porque su propósito es devolver instancias de tarjetas a la interfaz:
Copiar códigoJAVASCRIPT
_getTemplate() {
const cardElement = document
.querySelector(".card-template")
.content
.querySelector(".card")
.cloneNode(true);

return cardElement;
}

generateCard() {
// Almacenar el marcado en el campo privado _element
// para que otros elementos puedan acceder a él
this._element = this._getTemplate();

// Añadir datos
this._element.querySelector(".card__avatar").src = this._image;
this._element.querySelector(".card__paragraph").textContent = this._text;

// Devolver el elemento
return this._element;
}
Ahora, vamos a crear un método que itere sobre los elementos del array messageList para
crear nuevas instancias de la clase Card y añadirlas al DOM:
Copiar códigoJAVASCRIPT
// inicio del archivo index.js

const messageList = [
{
image:
"https://practicum-content.s3.us-west-1.amazonaws.com/web-code/moved_card__image.jpg",
text: "Hola, ¡tenemos que configurar nuestro chat lo antes posible!"
},
{
image: "https://practicum-content.s3.us-west-1.amazonaws.com/web-code/moved_card__image-
lake.jpg",
text: "¡Ahora podemos crear tantas tarjetas como necesitemos!"
},
];

class Card {
// código de la clase
}

messageList.forEach((item) => {
// Crea una instancia de tarjeta
const card = new Card(item.text, item.image);
// Rellena la tarjeta y devuélvela
const cardElement = card.generateCard();

// Agrégala al DOM
document.body.append(cardElement);
});
Aquí está el resultado:

Ahora es realmente fácil añadir nuevas tarjetas, ya que lo único que tenemos que hacer es
añadir datos al array. ¡Misión cumplida! Hemos realizado una diferenciación de funciones.
Nuestros datos están gestionados dentro de nuestro array, mientras que nuestra clase está
gestionando la visualización de esos datos. De este modo, todo quedará claro a medida que
nuestro proyecto continúe desarrollándose.
Puedes ver el código final en repl.it (materiales en inglés). Haz clic en "ejecutar" para ver
cómo funciona
Sprint 8
Capítulo 4/10
Interfaces en POO — Parte 1

Escalar una clase de JavaScript


No todas las clases son tan sencillas como las que hemos visto en las lecciones anteriores.
Muchas veces tenemos que estar dos pasos por delante e intentar prever qué funcionalidad
debería ser modificada debido al escalado del proyecto, y cómo estos cambios
influenciarían tu código.
Esto es algo que los desarrolladores y desarrolladoras siempre tienen que tener en cuenta.
Vamos a echar un vistazo a algunos ejemplos para ver cómo estos retos pueden
desempeñarse en un proyecto en la vida real.

¿Cómo se pasa un objeto al método constructor()?


A menudo, puedes necesitar más datos en tu clase:
Copiar códigoJAVASCRIPT
// itera sobre todo el array original
messageList.forEach((item) => {
// necesitamos pasarle muchos argumentos
const card = new Card(item.text, item.image, item.user.id, item.user.name, item.date);
const cardElement = card.generateCard();

document.body.append(cardElement);
});
Puedes ver que, al ser tan numerosos, los argumentos que estamos pasando al crear
nuestra Card se nos van un poco de las manos. Tener tantos parámetros puede ser confuso y
además perjudica la legibilidad de nuestro código. ¡Pero hay una solución! Podemos pasar
un objeto cuando creamos una clase, y luego, asignar nuestras propiedades dentro de la
propia clase:
Copiar códigoJAVASCRIPT
class Card {
constructor(data) { // ahora el constructor obtiene un objeto
this._text = data.text;
this._image = data.image;
}

// el resto de los métodos de la clase Card


}

// itera sobre todo el array original


messageList.forEach((item) => {
const card = new Card(item); // pasa un objeto como argumento
const cardElement = card.generateCard();
document.body.append(cardElement);
});
¿Cómo se utiliza una clase con diferentes elementos de
plantilla?
Ahora vamos a preparar nuestra clase para recibir la <template> que alberga el marcado de
nuestros mensajes. ¿Por qué? Bueno, imaginemos que el diseñador de nuestra startup ha
estado trabajando en algunos nuevos diseños de chat. Cada diseño tiene los elementos
dispuestos y con un estilo diferente y, en el futuro, nuestro equipo lanzará una actualización
para que nuestros usuarios puedan personalizar el aspecto y estilo de los chats.
En nuestro ejemplo, obtenemos la plantilla del DOM mediante el método .querySelector(),
pero también puedes utilizar el selector dentro del método constructor(). Si conseguimos que
nuestra clase pueda tratar con cualquier tipo de plantillas y marcados, podremos crear
tarjetas basadas en cualquier número de modificadores, por
ejemplo card_size_large o card_size_small. Gracias a este cambio, nuestra clase Card será más
universal:
Copiar códigoJAVASCRIPT
class Card {
constructor(data, cardSelector) { // se añade el segundo parámetro
this._text = data.text;
this._image = data.image;
this._cardSelector = cardSelector; // se asigna el selector al campo privado
}

_getTemplate() {
const cardElement = document
.querySelector(this._cardSelector) // utiliza this._cardSelector aquí
.content
.cloneNode(true);

return cardElement;
}
}
Como ahora determinamos el tipo de tarjeta según su modificador en el elemento de
plantilla, estamos listos para crear nuevos tipos de tarjetas:
Copiar códigoHTML
<template class="card-template card-template_type_default">
<div class="card">
<img src="" alt="Userpic" class="card__avatar">
<div class="card__text">
<p class="card__paragraph"><!--el texto del mensaje aparecerá aquí --></p>
</div>
</div>
</template>
De hecho, ahora la clase puede trabajar con una plantilla completamente nueva:
Copiar códigoJAVASCRIPT
messageList.forEach((item) => {
// pasa el selector de plantilla en la creación
const card = new Card(item, ".card-template_type_default");
const cardElement = card.generateCard();

document.body.append(cardElement);
});
Gracias a este pequeño cambio, esta clase es ahora apta para ser escalada. Puedes ver
el código final de la lección en repl.it (materiales en inglés). Qué bien se siente uno cuando
planifica con antelación, ¿verdad? Es como si fuéramos ardillas en septiembre recogiendo
nueces para el invierno. ¡Pasemos a los ejercicios!

Sprint 8
Capítulo 4/10
Interfaces en POO — Parte 1

Controladores de eventos
El director del proyecto ha decidido añadir una funcionalidad que permita a los usuarios
seleccionar sus propios mensajes:
La tarea parece bastante sencilla. Tan solo tenemos que añadir un detector de eventos de
clic al mensaje y hacer que podamos cambiar a la clase activa card__text_is-active. Vamos a
describir todas estas operaciones dentro de nuestra clase.
Nuestro elemento está situado dentro de la propiedad privada this._element. Necesitamos
seleccionar el texto dentro del elemento, así como añadirle una nueva clase. Luego,
crearemos un método llamado _handleMessageClick(). Lo haremos privado ya que no
necesitamos acceder a este método desde fuera.
Copiar códigoJAVASCRIPT
class Card {
// constructor y otros métodos de la tarjeta

// se ha añadido el método _handleMessageClick


_handleMessageClick() {
this._element.querySelector(".card__text").classList.toggle("card__text_is-active");
}
}
Hay dos formas de añadir un detector de eventos. Podemos añadir un detector de eventos
dentro del propio método generateCard(), o también podemos crear un método privado aparte
llamado _setEventListeners(), que instalará los detectores de eventos.
Por experiencia sabemos que nuestro director de proyecto es capaz de darnos un número
infinito de tareas, así que siempre podemos esperar que nos pidan unos cuantos detectores
de eventos más. Ante este hecho, vamos a utilizar el método
independiente _setEventListeners() para evitar sobrecargar el código dentro de generateCard():
Copiar códigoJAVASCRIPT
_setEventListeners() {
this._element.querySelector(".card__text").addEventListener("click", () => {
this._handleMessageClick();
});
}

_handleMessageClick() {
this._element.querySelector(".card__text").classList.toggle("card__text_is-active")
}
Cuando conectamos nuestro detector de eventos, utilizamos la función arrow. Si intentas
hacer uso de una función común, this funcionará de forma distinta, así que por ahora, solo
utilizaremos funciones arrow para los callbacks de nuestros detectores. En los siguientes
capítulos profundizaremos en la sintaxis de las funciones y el operador this dentro del
cuerpo de las funciones arrow. En este caso, solo la función arrow permitirá el acceso
a _handleMessageClick() a través de this dentro del cuerpo del detector de eventos.
Vamos a añadir los controladores de eventos dentro de generateCard():
Copiar códigoJAVASCRIPT
generateCard() {
this._element = this._getTemplate();
this._setEventListeners(); // añade los controladores de eventos

this._element.querySelector(".card__avatar").src = this._image;
this._element.querySelector(".card__paragraph").textContent = this._text;

return this._element;
}
El código final tiene el siguiente aspecto:
Copiar códigoJAVASCRIPT
class Card {
constructor(data, cardSelector) {
this._text = data.text;
this._image = data.image;
this._cardSelector = cardSelector;
}

_getTemplate() {
const cardElement = document
.querySelector(this._cardSelector)
.content
.querySelector(".card")
.cloneNode(true);

return cardElement;
}

generateCard() {
this._element = this._getTemplate();
this._setEventListeners();

this._element.querySelector(".card__avatar").src = this._image;
this._element.querySelector(".card__paragraph").textContent = this._text;

return this._element;
}

_setEventListeners() {
this._element.querySelector(".card__text").addEventListener("click", () => {
this._handleMessageClick();
});
}

_handleMessageClick() {
this._element.querySelector(".card__text").classList.toggle("card__text_is-active");
}
}

messageList.forEach((item) => {
const card = new Card(item, ".card-template_type_default");
const cardElement = card.generateCard();

document.body.append(cardElement);
});
Ahora la clase es bastante flexible, asi que podemos manejar fácilmente los eventos de
usuario en cualquier tarjeta. Podemos simplemente crear nuevos métodos según sea
necesario y añadirlos a la lista de detectores dentro de _setEventListeners().
Puedes ver el código final en repl.it (materiales en inglés).
Sprint 8
Capítulo 4/10
Interfaces en POO — Parte 1

Herencia
En la lección anterior, terminamos de hacer las tarjetas de mensajes, y la buena noticia es
que nuestro director de proyecto está satisfecho con el resultado. Sin embargo, no hay
tiempo para descansar porque la interfaz aún no está terminada. Todavía necesitamos que
nuestras tarjetas cambien de aspecto y funcionalidad.
El diseñador nos ha informado de que las tarjetas con los mensajes del usuario deben
colocarse a la derecha y sin avatar, mientras que los mensajes del usuario del otro extremo
deben aparecer a la izquierda junto con su avatar.
Más adelante, habrá aún más diferencias entre estas tarjetas de chat. Pero por ahora,
veamos una maqueta del diseño del chat que pretendemos crear:

Ya parece un chat bastante decente, ¿no?


Tendremos que reconstruir la clase Card para implementar la idea del diseñador. El código
está organizado siguiendo los principios POO, por lo que podemos aplicar la herencia aquí.
Vamos a dejar los datos y las funcionalidades comunes en la clase padre, y estableceremos
las características únicas en las clases hijas.
Trabajaremos con la siguiente estructura:
Copiar códigoJAVASCRIPT
class Card {
// aquí tenemos los datos y las funcionalidades comunes
}

class UserCard extends Card {


// datos y funcionalidades de la tarjeta del usuario
}

class DefaultCard extends Card {


// datos y funcionalidad de la tarjeta de la persona que está en el otro extremo
}
Aquí hay dos elementos <template> con diferente marcado para cada tipo de tarjeta. Recuerda
que en las lecciones anteriores ya preparamos nuestra clase padre para trabajar con
diferentes selectores:
Copiar códigoHTML
<template class="card-template card-template_type_user">
<div class="card">
<!-- marcado de la tarjeta del usuario, que va sin imagen-->
</div>
</template>

<template class="card-template card-template_type_default">


<div class="card">
<!-- marcado de la tarjeta para la persona que está en el otro extremo -->
</div>
</template>
Ahora podemos pasar los selectores de la clase .card-template_type_user y .card-
template_type_default al constructor. Los métodos para obtener el marcado y generar las tarjetas
ya están escritos. Sin embargo, hay una diferencia en el propio marcado, con lo cual
redefiniremos esta funcionalidad en las clases hijas.
También hay que cambiar el array messageList. Necesitamos distinguir los mensajes del
usuario de los mensajes del otro miembro del chat. De lo contrario, no seremos capaces de
entender qué tipo de tarjeta hay que crear para cada mensaje en particular.
Con este fin, vamos a añadir una propiedad booleana llamada isOwner al objeto de mensaje
del usuario y marcarla como true para poder realizar comprobaciones basadas en este
parámetro:
Copiar códigoJAVASCRIPT
const messageList = [
{
image: "https://practicum-content.s3.us-west-1.amazonaws.com/web-code/moved_card__image.jpg",
text: "Hola, ¡tenemos que configurar nuestro chat lo antes posible!"
},
{
text: "Aquí tienes la tarjeta del usuario del chat".
isOwner: true // la propiedad isOwner se ha agregado al mensaje del usuario
},
{
image: "https://practicum-content.s3.us-west-1.amazonaws.com/web-code/moved_card__image.jpg",
text: "¡La respuesta!"
}
];
Ahora tenemos que determinar qué propiedades y métodos permanecerán en la clase padre,
y cuáles pasarán a las clases hijas.

¿Cómo se colocan los métodos en las clases?


Vamos a decidir en qué clase estará cada uno de los métodos:

 _getTemplate() irá a la clase padre. Este método obtiene una plantilla de marcado
utilizando un selector específico. Esto lo va a necesitar cada tarjeta.
 _setEventListeners() y _handleMessageClick() irán a la clase padre. Estos métodos asignan
funcionalidad a nuestras tarjetas en función de ciertos eventos. Todas nuestras
tarjetas necesitarán trabajar con eventos.
 generateCard() irá a la clase hija. Este método permite llenar las tarjetas de datos y
funcionalidad. En este momento, tenemos dos tipos de tarjetas con datos diferentes,
así que cada una necesitará su propio método.

El código se ve así:
Copiar códigoJAVASCRIPT
class Card {
constructor() {
// vamos a tratar con el constructor un poco más tarde
}

_getTemplate() {
// código de _getTemplate
}

_setEventListeners() {
// código de _setEventListeners
}
_handleMessageClick() {
// código de _handleMessageClick
}
}

class UserCard extends Card {


constructor() {
// vamos a tratar con el constructor un poco más tarde
}

generateCard() {
// código de generateCard
}
}

class DefaultCard extends Card {


constructor() {
// vamos a tratar con el constructor un poco más tarde
}

generateCard() {
// código de generateCard
}
}
Parece que el código del método apenas ha sufrido cambios, ¿verdad? Pero en el caso de
que llamemos a los métodos de la clase padre dentro de nuestras clases hijas, tendremos
que escribir super en lugar de this. Esto se debe a que nos estamos dirigiendo a la clase padre
y no a la clase hija.
Por lo tanto, el código del método cambiará. Este es el código del
método generateCard() dentro de la clase hija UserCard:
Copiar códigoJAVASCRIPT
class UserCard extends Card {
constructor() {
// vamos a tratar con el constructor un poco más tarde
}

generateCard() {
this._element = super._getTemplate(); // this reemplazado por super
super._setEventListeners(); // tthis reemplazado por super

this._element.querySelector(".card__paragraph").textContent = this._text;

return this._element;
}
}

¿Cómo se cambian los constructores padre e hijo?


En primer lugar, vamos a averiguar qué datos se almacenan en los constructores hijos, y
cuáles en los constructores padres. Actualmente, todas las propiedades se declaran a nivel
de Card. Pero no todas las clases hijas utilizan estas propiedades. Por ejemplo, la clase
UserCard no tiene un avatar, aunque la clase padre Card sí que tiene esos datos.
En definitiva, es necesario pasar el selector a las clases hijas. Este será el único parámetro
que dejaremos en la clase padre. El argumento del selector se pasa a las clases hijas
mediante la palabra clave super. Fíjate que mientras que el constructor de la clase Card tiene
un solo parámetro, al constructor hijo se le pasa un parámetro adicional (un objeto
llamado data):
Copiar códigoJAVASCRIPT
class Card {
constructor(cardSelector) { // ahora aquí hay solo un parámetro - el selector
this._cardSelector = cardSelector;
}

// todos los métodos de la clase van a continuación


}

class UserCard extends Card {


constructor(data, cardSelector) {
// la palabra clave super llama al constructor de la clase padre
// con un solo argumento que es el selector de plantilla
super(cardSelector);

// la tarjeta de usuario solo tiene texto


this._text = data.text;
}

// el método generateCard() va a continuación


}

class DefaultCard extends Card {


constructor(data, cardSelector) {
// llama al constructor del padre de la misma manera
super(cardSelector);

// la persona que está en el otro extremo tiene ahora un avatar y un texto


this._text = data.text;
this._image = data.image;
}

// generateCard() va a continuación
}

¿Cómo se crean las instancias de la clase?


Ahora tan solo tenemos que añadir una condición para iterar sobre nuestro array messageList.
Recuerda que se pueden crear diferentes instancias de la clase en función del valor de la
propiedad isOwner, así que vamos a utilizarla:
Copiar códigoJAVASCRIPT
messageList.forEach((item) => {
// Si el valor de isOwner === true,
// se crea una instancia de UserCard,
// en caso contrario se crea DefaultCard

const card = item.isOwner ?


new UserCard(item, ".card-template_type_user") :
new DefaultCard(item, ".card-template_type_default");

const cardElement = card.generateCard();

document.body.append(cardElement);
});
Echemos un vistazo al código final (materiales en inglés) antes de pasar a los ejercicios.
Sprint 8
Capítulo 4/10
Interfaces en POO — Parte 1
Polimorfismo
Cuando se trabaja con el DOM, a veces puede ser útil ampliar las características de sus
elementos dinámicos. Los elementos del mismo tipo tienen un comportamiento específico
acorde con su subtipo. Esto sucede cuando el usuario hace clic en una tarjeta y la diseña de
manera diferente.
El polimorfismo nos ayudará aquí. Vamos a ampliar la funcionalidad de una de las clases
hijas, de modo que esta funcionalidad esté disponible en todas las tarjetas de dicha clase.
Sigamos mejorando nuestra página de chat. En la clase padre Card, el
método _handleMessageClick() activa la clase "card__text_is-active" siempre que el usuario haga
clic en el elemento de clase ".card__text".
Esta clase resalta el texto de la tarjeta con un color diferente:
Copiar códigoJAVASCRIPT
_handleMessageClick() {
this._element.querySelector(".card__text").classList.toggle("card__text_is-active");
}
Nuestro objetivo es hacer que el método _handleMessageClick() de la clase hija UserCard sea más
sólido. Hasta ahora, este método solamente podía añadir una clase adicional al
elemento card__text.
Vamos a añadir una característica más a este método para que pueda cambiar el
atributo class del elemento HTML con la clase card.
El polimorfismo nos permitirá mejorar la funcionalidad de _handleMessageClick() dentro de
nuestra clase hija UserCard. Para ello, en primer lugar tendremos que llamar al método inicial
mediante la palabra clave super. En nuestro caso, llamaremos a super._handleMessageClick(). Una
vez hecho esto, podremos describir la funcionalidad adicional. Veamos algo de código para
que resulte más claro:
Copiar códigoJAVASCRIPT
class Card {
// el constructor y otros métodos

_handleMessageClick() {
this._element.querySelector(".card__text").classList.toggle("card__text_is-active");
}
}

class UserCard extends Card {


// el constructor y otros métodos

_handleMessageClick() {
super._handleMessageClick(); // llamando al método padre
// agregando una nueva funcionalidad a _handleMessageClick:
// this._element almacena un elemento de la tarjeta
// vamos a añadirle la clase card_is-active
this._element.classList.toggle("card_is-active");
}
}
Hemos implementado la anulación de métodos (overriding). Esto significa que hemos
ampliado la funcionalidad del elemento padre dentro de la clase heredada. Anulación de
métodos (overriding) - recuerda este término, ¡te lo vas a encontrar a menudo!
Puedes encontrar el código final aquí (materiales en inglés).
Este fue un ejemplo muy simple de polimorfismo en acción. En los proyectos reales,
normalmente necesitarás escribir un código de mayor complejidad, y dependerá de ti la
decisión de anular los métodos o reescribirlos completamente.
Sprint 8
Capítulo 4/10
Interfaces en POO — Parte 1

Interfaces en POO: Conclusión


En este capítulo, aprendiste a trabajar con el marcado utilizando la POO. Es un paradigma
complejo, pero estamos seguros de que a medida que pase el tiempo y tengas más
experiencia, lograrás entenderlo, como ocurre con cualquier otro concepto difícil.
Cuando trabajamos en un proyecto grande, a menudo, nuestro código puede volverse poco
manejable y difícil de gestionar, hasta que finalmente, perdemos el control sobre él. Sin
embargo, no tiene por qué ser así. La POO nos servirá de ayuda cuando nuestras
aplicaciones sean más grandes y cuando la misma funcionalidad se necesite en diferentes
lugares. La herencia, la encapsulación y el polimorfismo aceleran el proceso de desarrollo y
hacen que el mantenimiento de proyectos complejos sea más manejable.
En las entrevistas de trabajo, a los candidatos se les suele hacer preguntas sobre la
programación orientada a objetos. Por lo tanto, es muy recomendable familiarizarse con los
conceptos de herencia, encapsulación y polimorfismo y entender cómo de allí nace un
paradigma.
La POO nos ayuda a construir "fábricas" que producen bloques de código predefinidos. Te
has encontrado con esto mientras trabajabas con tarjetas de mensajes para la aplicación de
chat. Luego, como implementaste el polimorfismo y la herencia, todas estas tarjetas
tuvieron un toque diferente.
Necesitamos planificar el diseño y la estructura de nuestras aplicaciones por adelantado.
También necesitamos saber qué técnicas vamos a utilizar para que todo funcione. Si lo
hacemos, nos resultará mucho más fácil trabajar en nuestro proyecto.
Haciendo los ejercicios, puedes empezar a ver claramente cómo el código puede crecer
tanto, que ya no es conveniente almacenarlo en un solo archivo. En el próximo capítulo,
aprenderás cómo resolver este problema dividiendo tu código en módulos separados.
Sprint 8

Capítulo 5/10
Personal Branding for the Tech Industry

Marca personal para el sector tecnológico


En esta lección, hablaremos sobre qué puedes hacer para destacar como desarrollador/a. Te
enseñaremos qué son las propuestas únicas de valor y cómo crearlas, las habilidades transferibles y
cómo utilizarlas y, por último, los discursos de presentación, y por qué necesitas uno.

Propuesta única de valor y marca personal


Tu propuesta única de valor (con siglas en inglés UVP) es tu combinación de habilidades y
competencias que se ajustan a la demanda del mercado para convertirte en un activo deseable. Este
es también el núcleo de tu discurso de presentación. Durante las reuniones, entrevistas y eventos de
networking, utilizarás tu discurso una y otra vez. Por eso es buena idea pensar qué deberías decir y
empezar a practicar cuanto antes.

Tu marca personal es algo que muestras a ciertas personas a través de las entrevistas de trabajo, tu
currículum y las redes sociales. Consiste en cómo te percibe la gente.
El primer paso para desarrollar tu propuesta única de valor y tu marca personal es entender bien tus
puntos fuertes. En otras palabras, ¿qué es lo que hace que TÚ te consideres un profesional?
La creación de esta conciencia te permite descubrir la gama de habilidades y competencias que
aportas:
¿Qué temas te interesan? ¿Qué problemas puedes resolver? ¿Qué oportunidades creas? - Austin
Belcak, Marca Personal (materiales en inglés)
Ejemplos:
Hola, soy Terry. Diseñador de UX, filántropo, empresario y amante de los animales. Me
comprometo a ayudarte a desarrollar una marca personal que se ajuste a tu forma de ser y a tus
necesidades.
Soy Jake, un desarrollador web full stack con una formación STEM en Bioingeniería y mi pasión es
resolver problemas. Mi formación científica me permite utilizar mis habilidades analíticas para
construir productos web responsivos y fáciles de usar.
Soy Sam, un desarrollador web full stack con experiencia en gestión de proyectos. Además de
JavaScript, mi superpoder es la capacidad de comunicación y organización. Soy capaz de crear un
código limpio, pero también hacer que el proceso sea divertido y fluido para todo el equipo.
Puedes encontrar más perfiles
aquí: https://www.indeed.com/career-advice/career-development/personal-brand-statement-
examples (materiales en inglés)

Cómo posicionarte cuando eres nuevo en la industria tecnológica


La diversidad es bienvenida en la industria tecnológica, y muchos ingenieros tienen títulos no
relacionados con la tecnología. Es importante mencionar tu trayectoria y destacar los logros y
habilidades de tu anterior sector. Las competencias transferibles, como su nombre indica, son las
que aportas de tus funciones anteriores a tu futuro puesto. Pueden ser hard skills, como
conocimientos de software (Jira, Scram) o lenguajes de programación. También pueden ser soft
skills relacionadas con la comunicación, la capacidad de trabajar en equipo, el pensamiento crítico y
la atención al detalle.

Discurso de presentación
Cuando hayas descubierto tu UVP y tu marca personal, puedes empezar a pensar en tu discurso de
presentación. Un discurso de presentación, también conocido en inglés como elevator pitch, es una
sinopsis rápida de tu trayectoria y experiencia. Los discursos de presentación son una forma
elegante de presentarte que utilizarás en muchas ocasiones durante la búsqueda de empleo, e incluso
después. Por ejemplo, puedes utilizar tu discurso de presentación cuando te pongas en contacto con
otros desarrolladores durante eventos, cuando te presentes a la persona a cargo de la contratación
durante las entrevistas informativas o cuando te pongas en contacto con la empresa de contratación.
Los discursos de presentación se llaman así en inglés (elevator pitch) porque no deberían durar más
que un corto viaje en ascensor (de 30 a 60 segundos). La mejor estructura sería presente-pasado-
futuro: primero, explicas quién eres ahora, luego tu trayectoria y, por último, lo que quieres hacer
por la empresa en el futuro.
Cuando empieces a elaborar tu discurso de presentación, tienes que tener en cuenta algunas cosas:

 Tu formación, incluidos los aspectos técnicos de la misma, si es relevante.


 Cómo fue tu transición a la tecnología. Por ejemplo, "Yo estaba en la industria de la salud,
pero siempre me gustó la construcción de hardware. Empecé a experimentar con el software
y con el hardware y ahora soy un desarrollador de software".
 El valor que aportas a la empresa (teniendo en cuenta tu propuesta única de valor). Siempre
debes reconstruir tu discurso de presentación en función de la empresa o el entorno en el
que te encuentres. Por ejemplo, si la empresa quiere transformar el mundo de la educación,
piensa en cómo puedes contribuir a ello.
He aquí algunas preguntas adicionales sobre las que reflexionar a la hora de hacer una lluvia de
ideas para tu discurso de presentación.

Mira esta lista de reproducción de vídeos cortos del Taller de Carreras de TripleTen. Estos vídeos te
ayudarán a crear tu propuesta única de valor y tu discurso de presentación. Aprenderás los
conceptos básicos sobre cómo presentarte en público y verás algunos ejemplos en vivo de nuestros
alumnos.
Resumiendo, vamos a descubrir los principales errores del discurso de presentación.

Próximos pasos: aprender de los demás


Empieza a buscar personas (modelos de conducta, personas influyentes en el ámbito de la
tecnología) que trabajen en un puesto o un campo en el que quieras trabajar. Por ejemplo, si quieres
ser ingeniero de software en Spotify, debes buscar personas que desempeñen esa función, ver cuál
ha sido su trayectoria e identificar las cosas clave en sus perfiles. Además, puedes consultar tu
GitHub y encontrar allí sus datos de correo electrónico para conectar con ellos directamente,
entrevistarlos y preguntarles por su trayectoria. Eso puede ayudarte a decidir si quieres seguir
buscando trabajo en esa empresa. Aprenderemos más sobre este tipo de entrevistas y sobre
networking en nuestro Curso de Preparación Profesional, que podrás empezar en unos pocos
sprints.
Consejo profesional
Si has decidido que quieres solicitar empleo en una determinada empresa tecnológica para un
determinado puesto, y quieres conocer a gente de esa empresa, debes personalizar tu perfil y tu
discurso de presentación para que sean lo más relevantes para el conjunto de habilidades que
requiere ese puesto.
Cuando tengas tu discurso de presentación listo, sal a practicar. Preséntatelo a ti mismo en el espejo,
preséntaselo a tu familia y amigos, y después a tus compañeros y compañeras. Así, cuando tengas
que hacerlo en un entorno profesional, te sentirás mucho más cómodo y brillarás como la estrella
que eres.
Sprint 8
Capítulo 6/10
JavaScript modular

Introducción a JavaScript modular


Uno de los primeros problemas a los que se enfrentó Nabila con su proyecto "Community
Center Nano" fue el número de piezas con las que tenía que trabajar. Al principio, las ponía
en una enorme pila, por lo que le costó mucho tiempo y esfuerzo encontrar los bloques
adecuados. Pero ahora que los ha separado en dos montones por tamaño, tipo y color, cree
que terminará en poco tiempo. ¡Está haciendo que el tío Amir se sienta muy orgulloso!
Quizás te hayas dado cuenta de que, a medida que tu proyecto va creciendo, cada vez es
más difícil trabajar con un solo archivo. Este aumento de tamaño puede dar lugar a una
serie de problemas.
Obviamente, desplazarse hacia arriba y hacia abajo mientras se busca una línea de código
concreta puede resultar bastante irritante. Además, a medida que aumenta el número de
funciones y variables dentro de nuestro proyecto, surge otro problema. Las diferentes
funciones y variables deben tener nombres únicos porque un nombre solo puede almacenar
un valor. Esto puede provocar cierta confusión y errores.
Además, si hay un grupo de personas trabajando en el mismo archivo, te encontrarás con
constantes conflictos git.
Está claro que trabajar así no es lo ideal. Por eso dedicaremos este capítulo a los módulos,
que te ayudarán a dividir tu código en diferentes archivos, haciendo así que tu proyecto sea
más manejable.
Sprint 8
Capítulo 6/10
JavaScript modular

IIFE
Antes de pasar a hablar de los módulos, volvamos al tema del scope de las funciones. Así
estaremos preparados para presentarte un nuevo concepto.
Como sabes, una expresión de función es una función que se declara dentro de una
expresión. Ya estás familiarizado con los casos más comunes en los que podemos utilizar
expresiones de función:
Copiar códigoJAVASCRIPT
// una función almacenada en una variable
const double = function (num) {
return num * 2;
};

// una función pasada por un método


[1, 2, 3].map(function (item) {
return item * 2;
});
Como una expresión de función es una función que forma parte de una expresión; si
encerramos nuestra función dentro de un paréntesis, esta también se convertirá en una
expresión de función:
Copiar códigoJAVASCRIPT
/* es una expresión de función porque la función definida
en la expresión está entre paréntesis */

(function () {
console.log("¡Hola, mundo!");
});
Sin embargo, una función escrita de esta forma es inútil, ya que simplemente la estamos
declarando aquí sin llamarla ni pasarle nada. Como la función no se utiliza en ningún sitio,
el motor simplemente se deshará de ella. Esto lo podemos arreglar añadiendo algunos
paréntesis al final de nuestro código:
Copiar códigoJAVASCRIPT
(function () {
console.log("¡Hola, mundo!");
})(); // añadamos unos paréntesis al final del código para llamar a la función
Si ejecutamos este código, aparecerá en la consola el mensaje ¡Hola, mundo! La función se
ejecuta en cuanto se declara. Una función llamada y definida con este enfoque se llama
Expresión de Función Inmediatamente Invocada, o con sus siglas en inglés IIFE
(Immediately Invoked Function Expression).

Uso de las IIFEs contra las variables globales


Como cualquier otra función, las variables declaradas dentro de un IIFE son locales y no
serán visibles fuera de la función. Esto significa que si envolvemos nuestro código en un
IIFE, podemos deshacernos de todas las variables globales de nuestro código.
Aquí está el código con variables globales:
Copiar códigoJAVASCRIPT
const button = document.querySelector("button");

function handleClick(evt) {
// el código del controlador del evento clic
}

button.addEventListener("click", handleClick);
Y aquí está otra vez el código, esta vez con todas las variables locales:
Copiar códigoJAVASCRIPT
(function () {
const button = document.querySelector("button");

function handleClick(evt) {
// el código del handler del evento clic clickbutton
}

button.addEventListener("click", handleClick);
})();
Gracias a nuestro IIFE, no hay más variables globales dentro de nuestro código. Intentemos
pensar en cómo de útil es esto. Algo que puede ocurrir es que si otro desarrollador web
decide declarar una variable button, no afectará a tu código ni al suyo. Tu variable
permanece a salvo dentro del scope de tu IIFE.
Con este método, podemos ocultar nuestras variables del mundo exterior. Los IIFEs son
realmente geniales, ¿verdad? Su introducción contribuyó a la creación de los módulos de
JavaScript, de los que hablaremos en la próxima lección.
Sprint 8
Capítulo 6/10
JavaScript modular

Encapsulación y módulos
Todo este tiempo, hemos estado conectando nuestro código JS a nuestro HTML utilizando
la etiqueta <script>. Pero en realidad, podemos conectar librerías JS enteras de esta misma
manera, <script src="some-script.js">. Esta técnica está bien para una página web pequeña, pero
este enfoque no es práctico para proyectos más grandes que están siendo desarrollados por
un equipo de programadores. En esta situación, el proyecto se vuelve demasiado frágil.
Por ejemplo, si los archivos JS se conectan en el orden incorrecto, puede que la aplicación
pierda completamente su funcionalidad. Por ejemplo, si los archivos JS se conectan en el
orden incorrecto, puede que la aplicación pierda completamente su funcionalidad.
También existe el riesgo de una colisión global de nombres. Las personas que trabajan en
un equipo grande pueden utilizar involuntariamente los mismos nombres en dos variables
distintas y esto puede romper el código.
Básicamente, tenemos dos problemas. Necesitamos separar nuestro código en partes para
que sus funcionalidades no interfieran entre sí. También necesitamos que nuestros scripts
funcionen independientemente del orden en que estén conectados. La organización del
código ayudará a evitar estos riesgos.

Características de separación: Encapsulación


De acuerdo con el principio de encapsulación, todas nuestras características deben estar
definidas como objetos separados. Estos objetos deben tener sus propios métodos que
describan cualquier manipulación realizada a los objetos. Además, deberíamos poder
acceder a estos métodos desde fuera de la función sin preocuparnos por ningún problema de
scope. Todas las variables que podríamos necesitar deberían estar ya almacenadas dentro
del objeto. Veamos el siguiente ejemplo para ilustrar cómo se ve esto en la práctica:
Copiar códigoJAVASCRIPT
// el objeto Chart es un ejemplo de módulo
const Chart = (function() {
// las variables declaradas dentro del módulo serán locales, es decir, no se verán fuera de nuestra
función
const data = [];

// métodos públicos que se pueden utilizar para el objeto Chart


return {
render: function (data) { /* ... */ },
setData: function (data) { /* ... */ }
};
}()); // IIFE devuelve el objeto

Chart.render([[0,12], [1,22], [3,18]]);


Este enfoque modular de JavaScript fue inventado por el famoso desarrollador de JS
Richard Cornford en 2003. Este enfoque se dió a conocer después de que Douglas
Crockford lo describiera en su libro, JavaScript: The Good Parts, que se publicó en 2008.

Los módulos de JavaScript se convierten en estándar


Así, sin más, nuestros dos problemas se han resuelto. Ahora nuestro código puede dividirse
en módulos separados que funcionan de forma independiente, y está protegido contra las
colisiones de nombres.
A pesar de esto, todavía no hemos visto cómo conectar de forma segura nuestros scripts JS
con nuestro código HTML. Durante años, al conectar sus archivos, los desarrolladores
tenían que controlar cuidadosamente en qué orden lo hacían. Cada archivo podía hacer
referencia al código del anterior, pero los archivos que se conectaban antes no podían hacer
referencia al código contenido en los archivos posteriores.
Para solucionar este problema, ha habido varios intentos de estandarizar el uso de módulos
en JavaScript. Durante un tiempo, incluso se utilizaron varias normas simultáneamente.
Afortunadamente, el desarrollo de un único estándar comenzó en 2010 y, 5 años más tarde,
se publicó el estándar ES6, que realmente liberó el potencial de los módulos JS.
Sprint 8
Capítulo 6/10
JavaScript modular

¿Qué son los módulos? ¿Cómo podemos


utilizarlos?
Un módulo es una unidad de código independiente que soporta una determinada
funcionalidad de un programa. En otras palabras, un módulo es simplemente un archivo
que contiene algo de código que implementa una funcionalidad particular.
Mediante los módulos, podemos dividir nuestro código en varios archivos más pequeños y
luego conectarlos según lo necesitemos. Imaginemos que necesitamos definir el
comportamiento de un icono de producto en un sitio de compras. El icono en sí puede ser
un módulo único. El usuario debe ser capaz de hacer clic en el icono de la lista de
productos para abrir la página de un producto concreto, añadirlo a su cesta y escribir una
reseña sobre él.
El icono estará anidado dentro de otros módulos, como la lista de productos. Trataremos
este aspecto a continuación. Pero antes de poder utilizar nuestro módulo de iconos de
productos, tendremos que conectarlo.

Cargar y utilizar módulos


Para dividir nuestro código en varios módulos y gestionar las interacciones entre ellos, hay
varios pasos que debemos seguir.
En primer lugar, el navegador tiene que detectar que tu página web está utilizando módulos.
Para conseguirlo, tendremos que añadir el valor module al atributo type cuando conectemos
nuestros scripts:
Copiar códigoHTML
<script type="module" src="script-01.js"></script>
<script type="module" src="script-02.js"></script>

Exportar desde un módulo


Podemos hacer que ciertas variables y funciones de este módulo estén disponibles para
otros archivos. Este proceso se llama exportación. Por norma general, encontrarás las
exportaciones en la parte inferior de un archivo de módulo. Para exportar una función o una
variable, tendremos que añadir la sentencia export delante de la declaración de la función.
Esto suele hacerse al final del archivo del módulo:
Copiar códigoJAVASCRIPT
// script-01.js

export const str = "Soy una función del módulo script-01.js"

export function myFunc() {


console.log("Soy una función del módulo script-01.js");
}
En este punto, como los hemos exportado desde nuestro módulo, podemos importar la
variable str y la función myFunc en otros archivos.

Importar a un módulo
Después de exportar nuestras funciones y variables, podemos llevarlas a otro módulo
utilizando la sentencia import. Esta sentencia nos permite acceder a variables almacenadas
en otros archivos:
Copiar códigoJAVASCRIPT
// script-02.js

// importando la función y la variable utilizando sus nombres


import { str, myFunc } from "./script-01.js";

// ahora puedes usarlas

console.log(str); // "Soy una variable del módulo script-01.js"


myFunc(); // "Soy una función del módulo script-01.js"
Sin embargo, si simplemente creamos varios archivos JavaScript en nuestro PC e
intentamos conectarlos a nuestro documento HTML como módulos, no tendrá el efecto
esperado. En pocas palabras, no funcionará.
Para que todo funcione correctamente, tendremos que instalar un servidor local. Aprenderás
cómo hacerlo mientras trabajas en tu próximo proyecto.
Por ahora, pasemos a los ejercicios. Ya tenemos un servidor local en nuestra plataforma,
para que puedas practicar el trabajo con módulos.
Sprint 8
Capítulo 6/10
JavaScript modular

Sentencias export e import


Bien, llegados a este punto hablemos con más detalle de las sentencias export e import. Hay
dos formas de exportar e importar módulos de JavaScript: nombradas y por defecto.

Exportaciones e importaciones nombradas


Si tenemos varios primitivos, funciones u objetos que queremos exportar desde un módulo,
utilizaremos exportaciones e importaciones nombradas, las cuales afectan a entidades
únicas, como variables, objetos, funciones, etc.
Más adelante en esta lección veremos las exportaciones e importaciones por defecto, que
exportan un único valor retornado por un módulo. Si ahora mismo te parece todo un poco
confuso, no te preocupes. A medida que avancemos en las distintas formas de importar y
exportar en JavaScript, las diferencias entre ambas se irán aclarando.
Empezaremos hablando de las exportaciones mediante el nombre.

Exportar al declarar
Puedes exportar todo tipo de datos, incluidas las variables, las funciones y las clases,
cuando son declaradas o se escriben por primera vez:
Copiar códigoJAVASCRIPT
// exportar una variable
export const str = "Este string es un secreto";
export const date = [12, 22, 31];

// exportar una función


export function giveMeSomeInternet() {
return "¡Hola! Sí, esto es Internet.";
}

// exportar una clase


export class Song {
constructor() {

}
}

Exportar después de declarar


Podemos exportar estas clases más tarde si escribimos sus nombres dentro de un conjunto
de llaves { } como vemos a continuación:
Copiar códigoJAVASCRIPT
const str = "Voy a ser exportado";
export { str };
Puedes exportar, a la vez, varios primitivos, funciones u objetos de un módulo. Solo tienes
que separar sus nombres utilizando comas dentro de las llaves:
Copiar códigoJAVASCRIPT
const array = [1, 2, 3, 4];
const arrSquared = arr.map(item => item * item);

// exportar una lista


export { array, arrSquared };

Renombrar en el momento de exportar: export as


Puedes cambiar el nombre de los primitivos, funciones u objetos al exportar. Para ello,
simplemente escribe el nombre antiguo de tu función o variable seguido de as dentro de tu
sentencia export, luego añade el nuevo nombre que quieres utilizar. Una vez hecho esto,
podrás acceder a las entidades del módulo por sus nuevos nombres dentro del archivo
donde las has importado.
Copiar códigoJAVASCRIPT
// data.js

const array = [1, 2, 3, 4];


const arrSquared = arr.map(item => item * item);

// renombrar al exportar
export { array as arr, arrSquared as sq };
Copiar códigoJAVASCRIPT
// index.js

// utilizar nuevos nombres en la sentencia import


import { arr, sq } from "./data.js";

La sentencia import
Al igual que con las exportaciones, puedes importar una o varias exportaciones de un
mismo módulo utilizando llaves { }:
Copiar códigoJAVASCRIPT
// index.js

// para importar varias funciones del archivo data.js, enuméralas entre llaves y sepáralas por una coma
import { array, arrSquared } from "./data.js"
console.log(array); // [1, 2, 3]
console.log(arrSquared); // [1, 4, 9]
En algunos casos, puede ser necesario importar todo el contenido de un módulo, es decir,
todo lo que se puede exportar de él. Para ello, simplemente escribe un asterisco * después
de tu sentencia import, luego escribe as y finalmente un nombre de variable para almacenar el
contenido del módulo. Así es como se ve en el código:
Copiar códigoJAVASCRIPT
// index.js
import * as data from "./data.js";

// se importará todo lo que se haya exportado del archivo data.js

console.log(data.array); // [1, 2, 3]
console.log(data.arrSquared); // [1, 4, 9]
Sin embargo, no recomendamos esta forma de importar módulos. Este código puede
resultar poco claro y difícil de refactorizar, ya que no siempre sabrás exactamente qué
habrás importado del archivo data.js.

Renombrar en el momento de importar: import as


Puedes renombrar las entidades importadas de la misma manera que cuando las exportas.
Por ejemplo, puedes acortar los nombres de los módulos. Veamos este código para que nos
sirva de ejemplo:
Copiar códigoJAVASCRIPT
// index.js
import { array as arr, arrSquared as sq } from "./data.js"

console.log(arr); // [1, 2, 3]
console.log(sq); // [1, 4, 9]

Exportar e importar por defecto


Puedes tener varias exportaciones por módulo. Pero en caso de que solo necesites exportar
una entidad (normalmente es una clase o una función), es preferible utilizar una exportación
por defecto.
Para escribir correctamente la sintaxis de exportación por defecto, escribiremos export
default seguido del nombre de la entidad que queremos exportar.
Copiar códigoJAVASCRIPT
// render-items.js
export default function renderItems() {
// código de la función
}
A diferencia de las exportaciones e importaciones con nombre, los nombres que damos a
las exportaciones por defecto no importan en el momento de importarlas. Incluso puedes
exportar una función anónima, lo que es imposible cuando se utiliza una exportación
normal. En el siguiente ejemplo, te mostraremos cómo utilizar export default en algunos casos
comunes:
Copiar códigoJAVASCRIPT
// Imaginemos que las diferentes funciones de exportación a continuación
// están todas en archivos diferentes

// render-items.js
export default function render() {
// ...
}

// song.js
export default class {
constructor() { }
}

// data.js
export default [12, 22, 31];
Fíjate en que no utilizamos las llaves en las importaciones por defecto.
Copiar códigoJAVASCRIPT
// index.js

import renderItems from "./render-items.js";

renderItems();
Como estamos importando algo mediante una exportación por defecto, debemos
nombrarlos en el momento de la importación:
Copiar códigoJAVASCRIPT
import renderItems from "./render-items.js";
import Song from "./song.js";
import someArr from "./data.js";
Resumen
Sintaxis de export:

 export const array = [1, 2, 3] — exportar al declarar.


 export { dog, cat } — exportar múltiples entidades separadas de su declaración.
 export default data — exportar una sola variable, función o clase.

Sintaxis de import:

 import { array } from "./data.js" — importar utilizando el nombre original de una


característica.
 import * — importar todas las exportaciones.
 import { array as arr } from "./data.js" — función de cambio de nombre en la importación.
 import data from "./data.js"; — importar una exportación por defecto (no se necesitan
llaves).

Sprint 8
Capítulo 6/10
JavaScript modular

Características de los módulos específicos


del navegador
Siempre que añadas un atributo type="module" a una etiqueta <script>, los navegadores los
tratarán de forma diferente. En esta lección, discutiremos los cambios que un desarrollador
web puede encontrar al trabajar con módulos.

Scope del módulo


El módulo tiene su propio scope, por lo que las variables y funciones declaradas dentro de
un módulo no contaminan el espacio de nombres global. Esto también implica que si tus
variables y funciones se necesitan en algún lugar fuera del módulo, tendrás que exportarlas:
Copiar códigoJAVASCRIPT
// constants.js

export const numbers = [2, 3, 5];


Copiar códigoJAVASCRIPT
// index.js
import { numbers } from './constants.js';

const doubledNumbers = numbers.map(number => number * 2);

console.log(doubledNumbers); // [4, 6, 10]


Copiar códigoHTML
<!-- index.html -->

<script type="module" src="index.js"></script>


<script type="module" src="constants.js"></script>

<script>
console.log(numbers); // error - no existe tal variable en el scope global
console.log(doubledNumbers); // este tampoco se puede encontrar
</script>

Navegadores antiguos
En los navegadores más antiguos, el atributo type="module" no existe. Por lo tanto, si abres
una página web que contenga una etiqueta <script> configurada para módulos, tu JavaScript
no estará habilitado.
Podemos resolver este problema escribiendo un código adicional para los navegadores
antiguos, que asegurará que JavaScript esté habilitado. Solo tenemos que añadir una
etiqueta <script> más y escribir el valor "nomodule" en su type.
Copiar códigoHTML
<!-- este módulo se cargará si el navegador es moderno -->
<script type="module"></script>
<!-- este módulo se cargará si el navegador es antiguo -->
<script type="nomodule"></script>
Sin embargo, en la práctica se utiliza muy poco. Más adelante, estudiaremos herramientas
aún más avanzadas para trabajar con módulos. Entre otras cosas, estas herramientas harán
que nuestro código sea legible incluso cuando se utilicen navegadores antiguos.

Todo en el lugar correcto


Los módulos siempre se cargan después de que la página se haya renderizado. Así que, en
esencia, no importa dónde conectes tus módulos. Puedes hacerlo al principio o al final del
archivo, o en cualquier punto intermedio. Mostremos un poco la diferencia:
Copiar códigoHTML
<script>
console.log(document.querySelector('input')); // null
/* esto no funcionará sin módulos porque el campo de entrada está declarado
en el código que mostramos a continuación, lo que significa que todavía no podemos trabajar con él
*/
</script>

<script type="module">
console.log(document.querySelector('input')); // <input>

/* cuando utilizamos módulos, podemos acceder a nuestra entrada sin ningún problema */
</script>

<input type="text">
Dicho esto, siempre incluiremos los scripts al final de <body> para evitar estos problemas
que pueden aparecer.

Sprint 8
Capítulo 6/10
JavaScript modular

JavaScript modular: Conclusión


En este capítulo, hemos aprendido la sintaxis necesaria para utilizar módulos en JavaScript
y hemos visto cómo podemos dividir nuestro código en estos módulos.
Es importante tener en cuenta que si queremos trabajar con módulos, no podemos ejecutar
nuestros programas localmente sin la ayuda de algunas herramientas especiales. Si
intentamos ejecutar nuestro proyecto sin estas herramientas, nuestros archivos no estarán
conectados entre ellos, y nuestra consola se llenará de errores.
Trataremos este problema más adelante. Básicamente, para que todo funcione
correctamente, tendremos que instalar un servidor local y luego ejecutar nuestro proyecto
en ese servidor. Para ello, también necesitaremos instalar una extensión en VS Code. Pero
como hemos dicho, trataremos esto en profundidad, más adelante.
En este punto, llegó el momento de tomarte un descanso de la programación y centrarte en
tu futuro trabajo y la comunicación del equipo. En la siguiente lección, aprenderás sobre los
diferentes roles del equipo y descubrirás con quién tendrás que trabajar. Además, conocerás
tus responsabilidades dentro de un equipo. Después, volveremos al mundo de la
programación y practicarás el uso de POO y el trabajo con módulos.
Cuando estés preparado, seguimos.

Sprint 8
Capítulo 7/10

Trabajo en equipo

Trabajo en equipo
Imagina que has empezado un nuevo trabajo y estás deseando conocer a tus compañeros y
compañeras y formar parte del equipo. Sin embargo, nada más empezar, te encuentras con algunos
problemas, y te surgen muchas preguntas sobre cómo resolverlos.
En lugar de aconsejarte, un/a desarrollador/a senior te dice que leas la documentación. Más tarde, el
equipo de dirección te envía un montón de correcciones con cosas que tienes que cambiar. Después,
alguien de diseño te pregunta: "¿Podrías idear algo para que la página web sea adaptiva?".
Para evitar un sentimiento de frustración e impotencia, exploraremos las funciones de cada uno de
estos miembros del equipo y descubriremos por qué tus compañeros actúan así.

Comunicación con un/a desarrollador/a senior


Cuando una empresa paga por una hora de trabajo de un/a desarrollador/a senior, el trabajo que
realiza implica la implementación o el mantenimiento de funciones importantes. Además, un/a
desarrollador/a senior tiene que arreglar todos los bugs importantes antes de la fecha de
lanzamiento. Y, con frecuencia, la fecha límite suele ser ayer.
Por eso, cuando les pidas ayuda, lo más probable es que no recibas una respuesta directa. En su
lugar, te mandarán algunos consejos generales o un enlace a algunos documentos clave.
Obviamente, los/as desarrolladores/as senior quieren ayudar a sus compañeros, pero también
quieren que aprendan a resolver sus problemas de forma independiente. Nuestros tutores siguen el
mismo enfoque, por lo que tienden a indicarte la dirección correcta en lugar de limitarse a darte las
respuestas.

Mantener una buena relación con el equipo de dirección


Si el diseño de una aplicación móvil se rompe, o algún botón de un formulario deja de funcionar,
un/a gestor/a explicará todos estos problemas al cliente. Cuando te envían una tonelada de
correcciones, la jefatura lo hace para resolver cualquier problema que le pueda surgir al cliente (con
suerte, manteniendo tu ego intacto).
Es importante estar en sintonía con el/la jefe/a de equipo. Para ello, debes asegurarte de que todo
está claro en cuanto a tus términos de referencia y tus plazos. Si tienes alguna duda, no dudes en
preguntar. También recomendamos asistir a reuniones con los clientes, si es posible, para que, por
ejemplo, puedas explicarles por qué es mejor arreglar primero el botón de registro en lugar de
perder tiempo en arreglar el diseño para móviles.

Llevarse bien con los/as diseñadores/as


No dudes en ponerte en contacto con el equipo de diseño. Ayudará a resolver cualquier cuestión
relacionada con el trabajo de forma más eficaz. Algunos ejemplos:

 ¿Qué efecto hover debo utilizar?


 ¿Por qué la altura de este pie de página es diferente en la página de noticias?
 ¿Dónde puedo encontrar las fuentes?

Ten en cuenta que no es tarea del personal de diseño entender todo lo que está relacionado con el
desarrollo y la programación web. Puede que no sepan que la propiedad flex no es compatible con
las versiones más antiguas de IE o el tiempo que puede llevar crear una galería de imágenes
compleja.
Lo importante es discutir todo con calma y ayudarse mutuamente. No importa quién se haya
equivocado mientras el problema se resuelva con éxito.

Aprender a aceptar las críticas de los/as ingenieros/as que prueban el código


Los/as ingenieros/as de pruebas se encargan de probar el código y comprobar que todo funciona
correctamente antes de publicarlo. Suelen enviar un montón de informes de bugs al equipo de
desarrollo, y no siempre es obvio reproducir o solucionar estos fallos.
Al principio, esto hará que te quieras tirar de los pelos. Para lidiar más eficazmente con los bugs,
debes intentar crear un entorno de trabajo cómodo. Dedica un día de la semana a la corrección de
bugs y házselo saber a tu jefe/a.
Para hacer frente a los bugs, también recomendamos tener en cuenta la Técnica Pomodoro.

Escuchar a los especialistas en SEO


Aunque no entiendan exactamente cómo funciona una página web, saben de lo que hablan. Seguir
sus recomendaciones suele ayudar a mejorar las métricas de la página y a aumentar el tráfico. No
todo el mundo entiende el aspecto técnico de las cosas, pero los/as especialistas en SEO pueden
ayudar a que el negocio crezca y prospere.
Hacerse amigos del departamento de marketing
Al principio, parece que los especialistas en marketing siempre dicen algo así como: "Vamos a
añadir métricas y análisis a esta página. No sé cómo, pero crea un servicio de monitoreo". Crear un
servicio de monitoreo, claro. ¿Pero cómo? Al igual que con el personal de diseño y los/as
especialistas en SEO, es importante hablar de todo con los/as especialistas en marketing. Si una
determinada función es difícil de implementar, hay que decirlo inmediatamente. Intenta comprender
las ideas de tus colegas, y con cuál de ellas podrás obtener mejores resultados.
Supongamos que alguien de marketing quiere añadir una sección de noticias a la página web. Esta
característica no impulsará las ventas directamente y también requerirá el despliegue del servidor de
back-end y el uso del panel de administración. Explica cuidadosamente todo y aclara exactamente
por qué podría ser mejor centrarse en la creación de un catálogo de productos y un botón para pagar.

Resumen
Los miembros de tu equipo tienen sus propias vidas y problemas. Para trabajar con eficacia y
disfrutar de tu trabajo, debes relacionarte con tus compañeros y compañeras y conocer sus
responsabilidades. Esto te ayudará a comunicarte con mayor empatía y facilitará la resolución de
cualquier conflicto u otros posibles problemas.
Aquí tienes una lista de puntos clave que debes seguir para garantizar una buena comunicación con
tu equipo:

1. Completa tu parte del trabajo antes de la fecha límite


2. Si el plazo no es realista, no aceptes la tarea
3. Si sabes que vas a incumplir un plazo, avisa a tu equipo con antelación
4. En situaciones inesperadas, crea un plan B junto con los miembros de tu equipo

Una pregunta rápida. En resumen, lo más importante cuando se trabaja con un nuevo equipo es
hacerse valer.

En realidad no. Si empiezas a trabajar con esta actitud, no harás muchas amistades. Intenta ser más
empático con los demás. Esto no significa que tengas que pasar por el aro o ir a por todas para
complacer a todo el mundo. Siempre puedes decir que no cuando lo consideres necesario, pero ser
amable y compasivo con los miembros de tu equipo contribuye en gran medida a mejorar la
comunicación y a crear un ambiente agradable y productivo. Esto hará que tu lugar de trabajo sea
un sitio al que te apetezca ir.
Falso
No es la respuesta correcta esta vez, ¡pero sigue intentando!Inténtalo nuevamente

También podría gustarte