5:31 Declarando una variable. var nombre.
6:21 Asignando un valor a la variable nombre. nombre = Felix
7:32 Javascript no requiere el uso de punto y coma “;” al final de cada sentencia. Por convención se decidió el uso de comillas. Hay casos muy puntuales en los cuales si se requiere el uso obligatorio de “;”. En la sección de complementos dentro de una clase está esa info.
9:19 Signo + para concatenar texto
11:11 Podemos declarar y asignar variables en la misma línea. separando con una coma “,”. Ej. var nombre = Felix
, apellido = Vasquez
11:30 Javascript es un lenguaje débilmente tipado. No hay nada que especifique que una variable sea de tipo texto o número. Aquí se explica con varios ejemplos.
Los strings son cadenas de texto. Para indicar que estamos usando una cadena de texto debemos de colocar las comillas simples.
¿Cómo pasar un string a MAYÚSCULAS?
0:36 Invocando una función sobre una variable.
// Variables a usar en la clasevar nombre = `Felix`, Apellido = `Vasquez`;
nombreVariable.función(parámetros); // Teoría de la invocación de una función sobre una variable.
nombre.toUpperCase() // La función toUpperCase() nos ayuda a pasar el string a mayúsculas.
¿Cómo pasar un string a minúsculas?
1:22 La función toLowerCase() nos ayuda a pasar el string a minúsculas.
nombre.toLowerCase()
2:14¿Cómo acceder a una letra en particular del string dada su posición cómo parámetro?.charAt(parámetro)
Ejemplo: apellido.charAt(0)
nos devuelve V
Esta función requiere el paso de un parámetro para indicar la posición de la letra que queremos de vuelta. Valor numérico.
3:15¿Cómo saber cuántas letras tiene un string?.length
length, nos indica la cantidad de caracteres que tiene un string. No invocamos paréntesis porque esto es un atributo no una función. Solo se llama al atributo length.
4:48 Concatenando strings
A la vieja escuela
var nombreCompleto = nombre + ` ` + apellido
Concatenando en los nuevos tiempos se llama Interpolación de texto
var nombreCompleto = `${nombre}${apellido}`var nombreCompleto = `${nombre}${apellido.toUpperCase()}`// Hará mayúscula el apellido
7:55¿Cómo acceder a un substring dentro de un string?
var str = nombre.charAt(1) + nombre.charAt(2) // hará el trabajo de devolver “el”, pero la siguiente es una mejor opciónvar str = nombre.substr(1, 2) //Cómo parámetro pasamos: posición y número de caracteres que queremos extraer. En este caso extrae igualmente “el”
10:01 Desafío: Encuentra la última letra de su nombre
var nombre = `Felix`; // Definimos el nombrevar cantidadDeLetrasEnNombre = nombre.length; // Contamos cuántas letras tienevar ultimaLetraDeNombre = nombre.charAt(cantidadDeLetrasEnNombre - 1); // Devuelve “x”. Restamos 1 porque la numeración del string comienza a partir de cero.
1:21Sumando
//Para incrementar uno podemos hacerlo de las siguientes formas
edad = edad + 1; // o
edad += 1// Ambas arrojan el mismo resultado
2:01Restando
peso = peso - 2; // o
peso -= 2// Ambas arrojan el mismo resultado
5:06Tratando decimales
var precioDeVino = 200.3; //definimos precio del vinovar total = precioDeVino * 3; //multiplicamos x 3600.9000000000001//devuelve esta locura
La manera de almacenar decimales en JS no es tan precisa debido a que destina cierta cantidad de bytes en la ram para asignar un decimal. Esto no permite que sea preciso. Salvo que se tengan librerías externas para esto.
Para solventar este problema hay varias opciones,
var total = precioDeVino * 100 * 3 / 100;
600.9//Esto funciona porque tiene un solo decimal
Pero si no estamos seguros de cuántos decimales son los que estamos usando en nuestro programa podemos hacer lo siguiente
var total = Math.round(precioDeVino * 100 * 3) / 100; //usando el módulo global de JS Math y lo redondeamos600.9//de esta manera va a ser un poco más preciso
Y para forzar dos decimales en el resultado a lo anterior agregamos
var totalStr = total.toFixed(2); // parámetro de la función toFixed 2 para indicar dos decimales
“600.90” //Esto devuelve un string
Y para transformarlo nuevamente a número podemos
var total2 = parseFloat(totalStr); //es la función punto flotante y devuelve/transforma a números decimales600.90//Esto devuelve un número otra vez
10:10Dividiendo
var pizza = 8;
var personas = 2;
var cantidadDePorcionesPorPersona = pizza / personas; // Esto devuelve 4
0:28 Creando una función que imprime el nombre y la edad de una persona
var nombre = `Felix`, edad = 41;
functionimprimirEdad() { // No tiene definido parámetrosconsole.log(`${nombre} tiene ${edad} años`)
}
imprimirEdad()
// Esta función tal como está no acepta parámetros. Solo imprime `Felix tiene 41 años`.```js
[2:51](https://platzi.com/clases/1339-fundamentos-javascript/12889-funciones61-3/?time=171) _Agregando parámetros a la función._ Para que pueda imprimir más nombres y edades debe aceptar parámetros y para eso haremos lo siguiente:
```js
var nombre = `Felix`, edad = 41;
functionimprimirEdad(n, e) { // Agregamos los parámetros nombre y edad a la función console.log(`${n} tiene ${e} años`) // La función tiene sus propios parámetros diferentes a los de la variable inicial.
}
imprimirEdad(nombre, edad) // Si bien estos parámetros `nombre y edad` hacen referencia a los valores `Felix y 41`, cuando lo pasemos dentro de la función su nombre será `n y e`.
De esa forma al pasarle los siguientes parámetros obtenemos lo siguiente
imprimirEdad(nombre, edad) // Devuelve `Felix tiene 41 años`
imprimirEdad(`Julia`, 28) // Devuelve `Julia tiene 28 años`
imprimirEdad(28, `Julia`) // Devuelve `28 tiene Julia años`
imprimirEdad() // Devuelve `undefined tiene undefined años`
Si una variable no está definida dentro del cuerpo de una función hablamos de una variable global. Por el contrario, una variable definida dentro de una función es una variable local.
1:51 Toda variable que no esté definida dentro de una función va a estar definida de forma global, por lo tanto puede ser accedida a través del objeto global, que en el browser es window
, por lo tanto puede ser accedida de la siguiente manera window.nombreVariable
var nombre = `Felix`;
functionimprimirNombreEnMayusculas() { // No se define ningún parámetro
nombre = nombre.toUpperCase() // Esta función devuelve un nuevo string con el string original pasado a mayúsculas. Ese string nuevo lo asignamos a la variable nombre modificando su contenido. `nombre` hace referencia a la variable externa fuera del scope y por tanto la modifica.console.log(nombre)
}
imprimirNombreEnMayusculas() // Invocamos la función sin parámetros porque no requiere ninguno
nombre // Imprime `FELIX` ya que la variable global fue modificada
Lo que está ocurriendo aquí es que la función está accediendo a la variable global.
2:51Side Effect.
Una función cómo la anterior al acceder a una variable que está fuera de su scope genera un efecto de lado (side effect).
Esto lo que significa es que al invocar la función va a generar efectos colaterales al modificar variables que no están definidas dentro de ellas, que no le pertenecen, y es algo que queremos evitar en nuestro código.
¿Cómo podemos mejorar esta función?
Para que la ejecución de una función no modifique una variable global usamos variables locales cómo parámetros en lugar de pasar directamente la variable. En la siguiente función definimos una variable local o parámetro dentro de nuestra función
var nombre = `Felix`;
functionimprimirNombreEnMayusculas(n) { // Definimos n cómo variable local y parámetro de nuestra función.
n = n.toUpperCase()
console.log(n)
}
imprimirNombreEnMayusculas(nombre) // Imprime `FELIX`. Invocamos la variable nombre y se la pasamos a la función cómo parámetro n
nombre // Imprime `Felix` ya que la variable global no ha sido modificada, la que se modificó fue n.
n // Imprime `Reference Error` porque no existe en el objeto global desde donde le estamos invocando
6:02 Es posible utilizar el mismo nombre para una variable global y para el parámetro de una función con un alcance local.
var nombre = `Felix`;
functionimprimirNombreEnMayusculas(nombre) { // Definimos nombre cómo variable local y parámetro de nuestra función. Esto significa que ya no podemos acceder a la variable nombre global// Desde acá adentro ya no podremos acceder a la variable nombre (global) a menos que lo hagamos de forma explícita window.nombre
nombre = nombre.toUpperCase()
console.log(nombre)
}
imprimirNombreEnMayusculas(nombre) // Imprime `FELIX`. Invocamos la variable nombre y se la pasamos a la función cómo parámetro nombre
nombre // Imprime `Felix` ya que invoca la variable global y esta no ha sido modificada, la que se modificó fue la variable nombre que existe dentro del scope de la función.
1:35 Vamos a empezar a trabajar con objetos, veremos cómo declararlos, cuáles son sus ventajas, cómo asignarles atributos y cómo trabajar con ellos dentro de las funciones.
Haremos una misma función de 5 formas diferentes:
4:46 La 1ra forma
var felix = {
nombre: `Felix`, // clave : valor
apellido: `Vasquez`, // clave : valor
edad: 28// clave : valor
};
var eduardo = {
nombre: `Eduardo`, // clave : valor
apellido: `Rodriguez`, // clave : valor
edad: 30// clave : valor
};
functionimprimirNombreEnMayusculas(nombre) { // Definimos variable local y parámetro `nombre`
nombre = nombre.toUpperCase()
console.log(nombre)
}
imprimirNombreEnMayusculas(felix.nombre) // Accedemos al objeto `felix` y al atributo `nombre` y lo pasamos a la función
imprimirNombreEnMayusculas(eduardo.nombre) // Imprime EDUARDO
nombre // Imprime `Reference Error` porque la variable nombre está dentro del scope de la función, y la invocamos desde un ámbito global.
5:27 2da Forma. Mejoraremos la función haciendo que reciba el objeto de la persona en vez de nombre
functionimprimirNombreEnMayusculas(persona) { // Definimos variable local y parámetro `persona`var nombre = persona.nombre.toUpperCase() // Creamos variable `nombre` para invocar el objeto y la clave `persona.nombre`console.log(nombre)
} // y cambiamos la forma de invocar la función a
imprimirNombreEnMayusculas(felix) // Invocamos a todo el objeto `felix` y se lo pasamos a la función
imprimirNombreEnMayusculas(eduardo) // Imprime EDUARDO
nombre // Imprime `Reference Error` porque la variable nombre está dentro del scope de la función, y la invocamos desde un ámbito global.
persona // igual `Reference Error`
6:07 3ra Forma. Si no queremos crear una nueva variable dentro de la función.
functionimprimirNombreEnMayusculas(persona) {
console.log(persona.nombre.toUpperCase()) // Insertamos la función dentro de la otra función
}
imprimirNombreEnMayusculas(felix) // Invocamos a todo el objeto `felix` y se lo pasamos a la función
imprimirNombreEnMayusculas(eduardo) // Imprime EDUARDO
6:40 4ta Forma. Desglosando el objeto dentro del parámetro de la función. Es una nueva funcioanlidad que viene con la nuevas versiones de JS.
functionimprimirNombreEnMayusculas({ nombre }) { // Colocamos los atributos del objeto que nos interesa invocarconsole.log(nombre.toUpperCase()) // Obtenemos el atributo nombre del objeto que le llegue.
}
imprimirNombreEnMayusculas(felix) // Imprime FELIX
imprimirNombreEnMayusculas(eduardo) // Imprime EDUARDO
8:22 5ta forma. Algo más a tener en cuenta si no queremos hacer referencia a la variable que contiene nuestro objeto
imprimirNombreEnMayusculas(felix) // Imprime FELIX
imprimirNombreEnMayusculas(eduardo) // Imprime EDUARDO
imprimirNombreEnMayusculas({ nombre: `Pepe` }) // Imprime PEPE
imprimirNombreEnMayusculas() // Imprime `Reference Error`
imprimirNombreEnMayusculas({ apellido: `Moreno` }) // Imprime `Reference Error`
IMPORTANTE: no podemos desglosar un objeto si no estamos seguros que siempre podremos pasarle el objeto que espera. Cómo vimos, en caso de pasar un valor undefined
la función se daña. Y lo mismo si pasamos un atributo que no está definido en el objeto cómo el atributo apellido
.
0:03 En esta clase aprenderemos otra forma de acceder a los atributos de los objetos que es la desestructurización de los mismos.
Para no duplicar las variables introducir el nombre de la variable como parámetro de la segunda variable. Ej var{nombre} = persona. Continuando con el ejemplo del punto anterior podemos expresarlo de la siguiente forma:
functionimprimirNombreEnMayusculas(persona) { // Colocamos nuevamente la variable/parámetro `persona`// var nombre = persona.nombre (Esta sentencia es igual a la siguiente). Para evitar la duplicación de `nombre` podemos hacerlo cómo siguevar { nombre } = persona // Crea una variable con el atributo(clave) del parámetro/variable `persona`// Esta es la forma más prolija si queremos acceder al atributo de un objeto y guardarlo en una variable del mismo nombre. En otras palabras, creamos la variable `nombre` que va a salir de `persona` console.log(nombre.toUpperCase())
}
imprimirNombreEnMayusculas(felix) // Imprime FELIX
imprimirNombreEnMayusculas(eduardo) // Imprime EDUARDO
1:40Reto de la clase: crea una función que imprimaHola, me llamo Felix y tengo 28 años
yHola, me llamo Eduardo y tengo 30 años
Usando los mismos objetos declarados en la clase procedemos así
var felix = {
nombre: `Felix`, // clave : valor
apellido: `Vasquez`, // clave : valor
edad: 28// clave : valor
};
var eduardo = {
nombre: `Eduardo`, // clave : valor
apellido: `Rodriguez`, // clave : valor
edad: 30// clave : valor
};
functionimprimirSaludo(persona) {
var { nombre } = persona // Extrayendo el atributo `nombre` y definiéndolo cómo variable para que console.log pueda utilizarlavar { edad } = persona // Si no definimos las variables internamente el browser arroja Errorconsole.log(`Hola, me llamo ${nombre} y tengo ${edad} años`)
}
imprimirSaludo(felix) // Imprime `Hola, me llamo Felix y tengo 28 años`
imprimirSaludo(eduardo) // Imprime `Hola, me llamo Eduardo y tengo 30 años`
0:33 1er ejemplo. Afectación de los objetos fuera del scope de la función
functioncumpleanos(persona) {
// persona.edad = persona.edad +1 (Esto equivale a lo mismo de abajo)
persona.edad += 1// Esta función incrementa en pasos de 1 la edad de persona y lo MODIFICA en el objeto original que está fuera del scope de la función
}
felix // Imprime `{nombre: "Felix", apellido: "Vasquez", edad: 28}` (objeto original)
cumpleanos(felix) // Ejecutamos la función, no hay nada que imprimir.
felix // Volvemos a imprimir el objeto `felix` y esta vez obtenemos `{nombre: "Felix", apellido: "Vasquez", edad: 29}`
😱 De esta forma se modifica el atributo edad del objeto original que está fuera de la función. Podemos decir que esta función tiene un Side Effect. OJO: esto puede ser un efecto deseado según la lógica de nuestro programa.
2:09 2do ejemplo. Evitando modificar el objeto original.
functioncumpleanos(edad) { // Cambiamos para que reciba cómo parámetro el atributo (o clave:valor) `edad`
edad += 1// Esta función incrementa en pasos de 1 el atributo edad de persona y NO MODIFICA el objeto original que está fuera del scope de la función. Solo se ha pasado el atributo edad, no el objeto completo.
}
felix // Imprime `{nombre: "Felix", apellido: "Vasquez", edad: 28}`
cumpleanos(felix.edad) // Pasa el atributo/clave:valor a la función. La ejecuta, no hay nada que imprimir.
felix // Volvemos a imprimir el objeto `felix` y esta vez obtenemos `{nombre: "Felix", apellido: "Vasquez", edad: 28}`
El atributo (o clave:valor) edad
del objeto no se modificó. Ya que no estamos pasando el objeto completo. Esto demuestra cómo JS se comporta diferente con los objetos, y cómo el valor objeto
es pasado y recibido cómo REFERENCIA, está referenciado al objeto original, por tanto se afecta cualquier cambio que se ejecute dentro de alguna función, aunque no parezca posible debido a la naturaleza de los scopes.
3:25 3er ejemplo. Creando y devolviendo un nuevo objeto. Este objeto tendrá la misma información de la persona excepto la edad, que será 1 más.
functioncumpleanos(persona) {
return { // Retornando un objeto nuevo que copia el objeto original (y sus atributos)
...persona, // Desglosamos a `persona` dentro de este nuevo objeto
edad: persona.edad + 1// y pisamos el atributo `edad`. Nos devuelve una persona más vieja.
}
}
felix // Imprime `{nombre: "Felix", apellido: "Vasquez", edad: 28}` (objeto original)var felixViejo = cumpleanos(felix) // Nos devuelve a `persona` con un año más `{nombre: "Felix", apellido: "Vasquez", edad: 29}`
felix // Volvemos a imprimir el objeto `felix` y obtenemos `{nombre: "Felix", apellido: "Vasquez", edad: 28}` porque no se ha modificado el objeto original.
Existen varias maneras de comparar variables u objetos dentro de javascript. En el primer ejemplo le asignamos a X un valor numérico y a Y un string. Para poder compararlos debemos agregar dos signos de igual (==). Esto los convierte al mismo tipo de valor y permite que se puedan comparar.
Cuando realizamos operaciones es recomendable usar tres símbolos de igual (===). Esto permite que JavasScript no iguale las variables que son de distinto tipo. Te recomendamos que uses el triple igual siempre que estés comparando variables.
Existen cinco tipos de datos que son primitivos:
Boolean
Null
Undefined
Number
String
0:52Comparando dos valores (primitivos)=
sirve para asignar un valor==
Compara dos valores, sin importar el tipo de dato.===
Compara dos valores y el tipo de dato.
x = 4, y = `4`;
x == y // Imprime `true`
x === y // Imprime `false` porque no es el mismo tipo de dato
3:26Comparando Objetos
var felix = {
nombre: `Felix`
}
var otraPersona = {
nombre: `Felix`
}
// Ambos objetos tienen la misma información, pero al compararlos pasa lo siguiente:
felix // imprime `Felix`
otraPersona // imprime `Felix`
felix == otra Persona // Imprime `false`.
Esto se debe a que JS compara (o pregunta) es por la referencia “variable”. Es decir compara las variables felix = otraPersona
y no su contenido, y las variables no son iguales.
Para lograr que sea igual debemos hacer lo siguiente:
var otraPersona = felix
// Esto hace que ambas variables apunten a la misma referencia en memoria y por lo tanto
felix == otraPersona // Imprime `true`. Son el mismo objeto en memoria.
felix === otraPersona // Igualmente
6:16Desglosando el objeto en un objeto nuevo
Siguiendo con el ejemplo anterior
var otraPersona = {
...felix // Creando un nuevo objeto
}
felix == otraPersona // Imprime `false`. Porque son dos objetos en posiciones de memoria diferente
felix === otraPersona // Igualmente
Para poder igualar los objetos podemos hacer lo siguiente
var otraPersona = felix
// Esto hace que ambas variables apunten a la misma referencia en memoria ram y por lo tanto
felix == otraPersona // Imprime `true`. Son el mismo objeto en memoria.
felix === otraPersona // Igualmente
7:31 ¿Qué pasa si modificamos uno de los objetos?
otraPersona.nombre = `Eduardo`
felix // Imprime `Eduardo` ya que ambos objetos apuntan a la misma posición de memoria ram.
var felix = {
nombre: `Felix`,
apellido: `Vasquez`,
edad: 28,
ingeniero: true,
cocinero: false,
cantante: true,
};
functionimprimirProfesiones(persona) {
console.log(`${persona.nombre} es: `)
if (persona.ingeniero) {
console.log(`Ingeniero`)
} else {
console.log(`No es Ingeniero`)
}
if (persona.cocinero) {
console.log(`Cocinero`)
} else {
console.log(`No es Cocinero`)
}
if (persona.cantante) {
console.log(`Cantante`)
} else {
console.log(`No es cantante`)
}
}
imprimirProfesiones(felix)
// imprime:
Felix es:
Ingeniero
No es Cocinero
Cantante
7:30Desafío de la clase:
Imprimir si una persona es mayor o menor de edad.
var felix = {
nombre: `Felix`,
edad: 28,
};
var eduardo = {
nombre: `Eduardo`,
edad: 15,
};
functionimprimirSiEsMayorOMenorDeEdad(persona) {
if (persona.edad >= 18) { // Magic Numberconsole.log(`${persona.nombre} es Mayor de edad`)
} else {
console.log(`${persona.nombre} es Menor de edad`)
}
}
imprimirSiEsMayorOMenorDeEdad(felix)
imprimirSiEsMayorOMenorDeEdad(eduardo)
const MAYORIA_DE_EDAD = 18// Definiendo esta variable evitamos el `Magic Number`. Un número que a priori nos puede confundir y requiere de un mayor esfuerzo para determinar en el contexto a que se refiere este número. Es una buena práctica.functionesMayorDeEdad(persona) { // Separamos la función que realiza la comparación de la función de impresión.return persona.edad >= MAYORIA_DE_EDAD // Esta función nos devuelve `true` o `false`
}
functionimprimirSiEsMayorOMenorDeEdad(persona) { // Queda cómo una función de impresión solamenteif (esMayorDeEdad(persona)) { // Toma el valor `true` or `false` para proseguir con la ejecuciónconsole.log(`${persona.nombre} es Mayor de edad`)
} else {
console.log(`${persona.nombre} es Menor de edad`)
}
}
En el libro jsparagatos.com hacen la siguiente aclaratoria:
“Hay 2 tipos principales de funciones:
-Funciones que modifican o crean valores y los retornan
-Funciones que toman valores y hacen cosas con ellos que no se pueden retornarconsole.log
es un ejemplo del segundo grupo: Imprime cosas en tu consola — una acción que podes ver con tus propios ojos pero que no puede ser representada como un valor de JavaScript.”
Les recomiendo su lectura como complemento a las clases.
En esta clase aprenderemos a utilizar Arrow Functions que permiten una nomenclatura más corta para escribir expresiones de funciones. Este tipo de funciones deben definirse antes de ser utilizadas.
Al escribir las Arrow Functions no es necesario escribir la palabra function, la palabra return, ni las llaves.
0:21 Declarando la función del ejercicio anterior cómo una función anónima. Para eso la declaramos dentro de una variable, cómo sigue
var esMayorDeEdad = function (persona) {
return persona.edad >= MAYORIA_DE_EDAD
}
Ahora el nombre de la función lo usamos para declarar la variable, y la función deja de tener un nombre, por eso se les llaman anónimas. La palabra function
va separada del parámetro (persona)
.
JS es uno de los pocos lenguajes que permite declarar funciones cómo variables.
2:12 Ahora veamos cómo se hace con arrows function.
var esMayorDeEdad = persona => persona.edad >= MAYORIA_DE_EDAD // Expresión más prolija.// La siguiente es una equivalente:const esMayorDeEdad = function (persona) {
return persona.edad >= MAYORIA_DE_EDAD
}
// Y esta también es equivalente:const esMayorDeEdad = (persona) => {
return persona.edad >= MAYORIA_DE_EDAD
}
// Y esta también es equivalente:const esMayorDeEdad = persona => {
return persona.edad >= MAYORIA_DE_EDAD
}
De esta forma reducimos la expresión de 3 renglones a 1 solo renglón.
OJO: Cuando se tiene un solo parámetro dentro de la función se pueden obviar los paréntesis ()
.
Si una función lo único que hace es retornar algo, podemos obviar la palabra return
y borrar las llaves {}
que rodean el cuerpo de la función, y de forma implícita va a devolver el valor de la comparación.
5:17 Desestructurando el objeto en la función de flecha
var esMayorDeEdad = ({ edad }) => edad >= MAYORIA_DE_EDAD // Colocamos los paréntesis para poder desestructurar
De esta forma estamos diciendo que: la variable esMayorDeEdad
es igual a la función anónima que tiene cómo parámetro ()
el atributo edad
del objeto {}
persona
, luego toma el atributo edad
y lo compara con la variable MAYORIA_DE_EDAD.
Está muy de moda escribir las funciones de esta forma.
Escribamos una función que use la comprobación de edad para permitir o no un acceso
functionpermitirAcceso(persona) {
if (!esMayorDeEdad(persona)) { // `!` se usa para negarconsole.log(`ACCESO DENEGADO`)
}
}
7:57 Desafío de la clase : escribe la función esMenorDeEdad
en forma de arrow function y que retorne la negación de la llamada a esMayorDeEdad
const MAYORIA_DE_EDAD = 18var esMenorDeEdad = ({ edad }) => edad < MAYORIA_DE_EDAD
functionpermitirAcceso(persona) {
if (!esMenorDeEdad(persona)) {
console.log(`ACCESO PERMITIDO`)
} else {
console.log(`ACCESO DENEGADO`)
}
}
OBSERVACIÓN: Durante el ejercicio (por error) coloque cómo nombre de la función el mismo de la variable esMenorDeEdad
, el resultado al ejecutar la función esMenorDeEdad(felix)
es el que retorna del function arrow de la variable true
o false
. Al ejecutar esMenorDeEdad sin pasarle ningún parámetro retorna la función almacenada en la variable ({ edad }) => edad < MAYORIA_DE_EDAD. Para su análisis 😬
for
, se utiliza para repetir una o más instrucciones un determinado número de veces.for
se coloca la palabra for
seguida de paréntesis y llaves.for
que calcula el peso que una persona aumenta o disminuye anualmente de forma random. Para la simulación random definiremos que un 25% de las veces subirá de peso, un 25% de las veces aumentará de peso y un 50% de las veces ninguna de las dos. Para eso simularemos el transcurso de un año:var felix = {
nombre: `Felix`,
edad: 28,
peso: 75,
};
console.log(`Al inicio del año ${felix.nombre} pesa ${felix.peso}kg`) // Imprime `Al inicio del año Felix pesa 75kg`const AUMENTO_DE_PESO = 0.2// 200gramosconst DIAS_DEL_ANO = 365const aumentarPeso = persona => persona.peso += AUMENTO_DE_PESO
const adelgazar = persona => persona.peso -= AUMENTO_DE_PESO
for (var i = 1 ; i <= DIAS_DEL_ANO ; i++) { // Simulación del transcurso de un año. // `var = i` Inicializa la variable que hará de contador.// i <= 365 Compara si la variable `i` es menor o igual a 365 // i++ incrementa en 1 cada iteración del ciclo `for`. Al dejar de cumplirse la condición `i <= 365` pasa a declarar la variable random con el valor aleatorio obtenidovar random = Math.random() // Crea un número aleatorio entre 0 (incluido) y 1 (excluido) en cada una de las 365 iteraciones que pasará a la siguiente función `if` if (random < 0.25) { // Comparamos y si es menor a .25
aumentarPeso(felix) // Suma el valor de 200gr al peso del objeto `felix` original. Lo afecta.
} elseif (random < 0.5) { // Si es mayor a .25 y menor a .5
adelgazar(felix) // Resta 200gr al peso del objeto `felix` original. Lo afecta
}
}
console.log(`Al final del año ${felix.nombre} pesa ${felix.peso.toFixed(1)}kg`) // Imprime `Al inicio del año Felix pesa (random value)kg`
Bonus: Agregamos la función kilosAnuales
para realizar el cálculo invocando a la función y no refrescando la página. Y adicionalmente le agregamos una fecha al objeto felix
para complementar el mensaje dandole más sentido con el año de la medida.
var felix = {
nombre: `Felix`,
edad: 28,
peso: 75,
ano: 2010,
};
const AUMENTO_DE_PESO = 0.2// 200gramosconst DIAS_DEL_ANO = 365const aumentarFecha = persona => persona.ano += 1// Agregamos la función que suma un año a cada iteración de 365 díasfunctionkilosAnuales(persona) {
console.log(`Al inicio del año ${persona.ano}, ${persona.nombre} pesaba ${persona.peso.toFixed(1)}kg`)
const aumentarPeso = persona => persona.peso += AUMENTO_DE_PESO
const adelgazar = persona => persona.peso -= AUMENTO_DE_PESO
for (var i = 1 ; i <= DIAS_DEL_ANO ; i++) {
var random = Math.random()
if (random < 0.25) {
aumentarPeso(persona)
} elseif (random < 0.50) {
adelgazar(persona)
}
}
console.log(`Al final del año ${persona.ano}, ${persona.nombre} pesó ${persona.peso.toFixed(1)}kg`)
aumentarFecha(persona)
}
kilosAnuales(felix) //Imprime// Al inicio del año 2010, Felix pesaba 82.0kg// Al final del año 2010, Felix pesó 90.4kg
kilosAnuales(felix) //Imprime// Al inicio del año 2011, Felix pesaba 90.4kg// Al final del año 2011, Felix pesó 86.7kg
kilosAnuales(felix) //Imprime// Al inicio del año 2012, Felix pesaba 86.7kg// Al final del año 2012, Felix pesó 91.5kg
while
que felix
fue al nutricionista y le mandó a bajar 3kg de peso. Y evaluaremos con dos funciones la ingesta de alimentos vs. el ejercicio que realiza. Vamos a medir cuántos días le va a tomar bajar esos 3kg.var felix = {
nombre: `Felix`,
edad: 28,
peso: 75,
};
const AUMENTO_DE_PESO = 0.3// 300gramosconst DIAS_DEL_ANO = 365functionkilosPerdidos(persona) {
const META = felix.peso - 3var dias = 0const aumentarPeso = persona => persona.peso += AUMENTO_DE_PESO
const adelgazar = persona => persona.peso -= AUMENTO_DE_PESO
const comeMucho = () => Math.random() < 0.3// el 30% de las veces va a aumentar de pesoconst realizaDeporte = () => Math.random() < 0.4// el 40% de las veces va a realizar deporte para bajar de pesowhile (persona.peso > META) { // Mientras el peso de `felix` sea mayor al peso actual menos 3, decimosif (comeMucho()) { // Si `comeMucho` es menor a 0.3, es `true` y por tanto
aumentarPeso(persona) // Ejecuta `aumentarPeso` sobre `felix`
}
if (realizaDeporte()) { // Si `realizaDeporte` es menor a 0.4, es `true` y por tanto
adelgazar(persona) // Ejecuta `adelgazar` sobre `felix`
}
dias += 1// Suma un día por cada iteración del `while`
}
console.log(`A ${persona.nombre} le tomó ${dias} días bajar 3Kg`)
}
kilosPerdidos(felix) // Imprime `A Felix le tomó xx días bajar 3kg`. Y cada vez que la ejecuto el objeto pierde 3kg.
Bonus: ¿Qué pasa si declaramos la variable META fuera de la función kilosPerdidos
?
En el ejemplo anterior cada vez que se ejecuta la función kilosPerdidos
el peso del objeto felix
se ve modificado debido al Side Effect
. El objeto original se ve modificado. Y por tanto la variable META, al estar referenciada al objeto original, disminuye en cada iteración 3Kg. Esto lleva a que con cada iteración el objeto vaya disminuyendo su peso hasta llegar a valores negativos, lo cual no hace mucho sentido desde el punto de vista lógico.
El Side Effect
lo aprendimos durante la clase 7 Alcance de las funciones y 10 Parámetros como referencia o como valor.
Al declarar la variable META fuera de la función lo que ocurre es que al ser declarada como una variable global de tipo const
esta no puede ser modificada. La variable META se declara con un valor inicial equivalente al peso del objeto felix
menos 3. Eso da como resultado el valor de 72. Este valor ya no cambia a pesar que el objeto original se haya visto modificado luego de la iteración. Al iterar la función kilosPerdidos(felix) lo que observamos es que se puede iterar solo una vez como se puede apreciar:
META // Imprime 72. Al ser global ahora sí podemos consultar su valor.
felix // Imprime {nombre: "Felix", edad: 28, peso: 75}
kilosPerdidos(felix) // Imprime “A Felix le tomó xx días bajar 3Kg” la primera vez.
kilosPerdidos(felix) // Imprime “A Felix le tomó 0 días bajar 3Kg” las siguientes veces.
META // Imprime 72.
felix // Imprime {nombre: "Felix", edad: 28, peso: 71.70000000000003} Ya no se modifica más.
Otra estructura repetitiva es el do-while. A diferencia de la instrucción while, un bucle do…while
se ejecuta una vez antes de que se evalúe la expresión condicional.
functionveSiLlueve() {
var contador = 0const llueve = () => Math.random() < 0.25do {
contador++
} while (!llueve())
console.log(`Fui a ver si llovía ${contador} veces`)
}
Reto de la clase, resuelve el bug que tiene el ejercicio. Cuando el resultado es 1 vez, entonces debemos cambiar el texto de veces
a vez
.
functionveSiLlueve() {
var contador = 0const llueve = () => Math.random() < 0.25do {
contador++
} while (!llueve())
if (contador > 1) {
console.log(`Fui a ver si llovía ${contador} veces`)
} else {
console.log(`Fui a ver si llovía ${contador} vez`)
}
}
Otra forma es usando Operador Ternario
functionveSiLlueve() {
var contador = 0const llueve = () => Math.random() < 0.25do {
contador++
} while (!llueve())
contador !== 1 ? console.log(`Fui a ver si llovía ${contador} veces`) : console.log(`Fui a ver si llovía ${contador} vez`)
}
if
.functionciudadFavorita() {
var ciudad = prompt(`Escribe tu ciudad favorita de Ecuador`, `Quito`)
switch (ciudad.toLowerCase()) {
case`quito` :
console.log(`Es mi favorita. Me encanta su clima!`)
breakcase`guayaquil` :
console.log(`Su gente es lo que más me encanta!`)
breakcase`ambato` :
console.log(`Es hermosa, amo su tranquilidad y su Pan! 😁`)
breakdefault :
console.log(`No conozco esa ciudad 😞`)
break
}
}
ciudadFavorita(QUIto) // Imprime`Es mi favorita. Me encanta su clima!`
Los arrays son estructuras que nos permiten organizar elementos dentro de una colección. Estos elementos pueden ser números, strings, booleanos, objetos, etc.
var sacha = {
nombre: 'Sacha',
apellido: 'Lifszyc',
altura: 1.72,
cantidadDeLibros: 111
}
var alan = {
nombre: 'Alan',
apellido: 'Perez',
altura: 1.86,
cantidadDeLibros: 78
}
var martin = {
nombre: 'Martin',
apellido: 'Gomez',
altura: 1.85,
cantidadDeLibros: 132
}
var personas = [sacha, alan, martin]
for (var i = 0; i < personas.length; i++) {
var persona = personas[i]
console.log(`${persona.nombre} mide ${persona.altura}mts`)
}
// Imprime:// Sacha mide 1.72mts// Alan mide 1.86mts// Martin mide 1.85mts
Los siguientes son formas equivalentes de acceder a un atributo de un array:
personas[0].altura // Imprime 1.72
personas[0][`altura`] // Imprime 1.72
filter ( )
crea una nueva matriz con todos los elementos que pasan la prueba implementada por la función proporcionada. Recuerda que si no hay elementos que pasen la prueba, filter devuelve un array vacío.filter
es la que va a iterar cada uno de los valores del array a la función esAlta
, va a recorrer cada valor del array uno a uno y va a devolver (en un nuevo objeto) solo aquellos que coincidan con nuestra condición. El objeto original no es modificado.Usando los objetos definidos en el ejercicio anterior seguimos de la siguiente manera
const esAlta = (persona) => {
return persona.altura > 1.8
}
var personas = [ sacha, alan, martin ]
var personasAltas = personas.filter(esAlta) // `filter` nos devuelve un nuevo array, el array original no se modificaconsole.log(personasAltas)
También se puede escribir de la siguiente manera
const esAlta = persona => persona.altura > 1.8var personas = [ sacha, alan, martin ]
var personasAltas = personas.filter(esAlta) // `esAlta` es la CONDICIÓN de nuestro filtrado, declarada arriba cómo funciónconsole.log(personasAltas)
Y una forma que podemos encontrar en internet es que incluyan la función dentro del filtro, como sigue
var personas = [ sacha, alan, martin ]
var personasAltas = personas.filter(function (persona) {
return persona.altura > 1.8
})
console.log(personasAltas)
Y la forma más prolija que es desglosando a persona
var esAlta = ({ altura }) => altura > 1.8// No nos interesa una variable llamada `persona` sino la `altura`var personas = [ sacha, alan, martin ]
var personasAltas = personas.filter(esAlta)
console.log(personasAltas)
Desafío: escribe el filtrado de personas bajas.
var esBaja = ({ altura }) => altura <= 1.8// Cambiando el signo de comparación logramos el resultadovar personas = [ sacha, alan, martin ]
var personasBajas = personas.filter(esBaja)
console.log(personasAltas)
var pasarAlturaACms = persona => {
persona.altura *= 100return persona
}
var personasCms = personas.map(pasarAlturaACms)
console.log(personasCms)
3:45 Retornando un nuevo objeto con la altura
modificada
var pasarAlturaACms = persona => {
return {
...persona, // Definiendo el nuevo objeto para no modificar el objeto original
altura: persona.altura * 100// Modificando el atributo `altura` de cada objeto lo llevamos a cmts
}
}
var personasCms = personas.map(pasarAlturaACms)
console.log(personasCms)
6:12 Agregando paréntesis al arrow function para devolver un objeto desde la función. Si el arrow function empieza con un paréntesis seguido de llaves, entonces podrá retornar el objeto que queremos. El objetivo aquí es eliminar la función return
explícita.
var pasarAlturaACms = persona => ({ // Agregamos paréntesis seguido de llaves para retornar un nuevo objeto// Sin los paréntesis estaríamos poniendo el cuerpo de la función y así no es posible que nos retorne el objeto
...persona,
altura: persona.altura * 100
})
var personasCms = personas.map(pasarAlturaACms)
console.log(personasCms)
OJO: map() devuelve un array nuevo con los criterios de filtrado que estemos aplicando, pero si en el proceso estamos modificando los atributos del objeto, estos si van a perdurar, ya que cómo objeto tratado por una función debemos recordar que está REFERENCIADO.
El método reduce() nos permite reducir, mediante una función que se aplica a cada uno de los elemento del array, todos los elementos de dicho array, a un valor único.
var acum = 0for (var i = 0; i < personas.length; i++) {
acum = acum + personas[i].cantidadDeLibros
}
console.log(`En total todos tienen ${acum} libros`) // Imprime `En total todos tienen 321 libros`
2:03 Ahora haremos lo mismo con reduce()
var reducer = (acum, personas) => {
return acum + personas.cantidadDeLibros
}
var totalDeLibros = personas.reduce(reducer, 0)
console.log(`En total todos tienen ${totalDeLibros} libros`) // Imprime `En total todos tienen 321 libros`
3:55 Versión más prolija del código anterior
var reducer = (acum, { cantidadDeLibros }) => acum + cantidadDeLibros
var totalDeLibros = personas.reduce(reducer, 0)
console.log(`En total todos tienen ${totalDeLibros} libros`) // Imprime `En total todos tienen 321 libros`
Las clases son funciones cuya sintaxis tiene dos componentes:
this
. Dentro de una función, el valor de this
depende de cómo es llamada ésta.Crear un prototipo es muy similar a crear una variable:
Vamos a definir el prototipo de Persona cuya función será saludar:
functionPersona(nombre, apellido) { // Creamos el Prototipo Persona que actúa como FUNCIÓN CONSTRUCTORA.this.nombre = nombre // Creando el atributo `nombre`this.apellido = apellido // El keyword ‘this’ se usa para generar nuevos parámetros o atributos dentro de la declaración del objeto.
} // Esta función no requiere `return` ya que la palabra reservada `new` que se usa en la siguiente instrucción retorna implícitamente el nuevo objeto. No se requiere hacer explícitamente.var felix = new Persona(`Felix`, `Vasquez`)
De este artículo extraemos lo siguiente para complementar la clase.
Todos los objetos dependen de un prototipo y que los prototipos son objetos, es más cualquier objeto puede ser un prototipo. De forma más concreta: un prototipo es un objeto del que otros objetos heredan propiedades.
Los objetos siempre heredan propiedades de algún objeto anterior, de este modo solo el objeto original y primigenio de javascript es el único que no hereda de nadie.
Ahora vamos a crear la función que permitirá saludar a todos los objetos creados por la función constructora.
functionPersona(nombre, apellido) {
this.nombre = nombre
this.apellido = apellido
}
Persona.prototype.saludar = function () { // El atributo `saludar` va a contener una función sin parámetros console.log(`Hola, me llamo ${this.nombre}${this.apellido}`) // que imprimirá el saludo.
} // Lo que hicimos fue decirle al prototipo de `Persona` que existe una atributo llamado `saludar` que contiene una función.var felix = new Persona(`Felix`, `Vasquez`)
felix.saludar() // Esta función invoca el saludo e imprime `Hola, me llamo Felix Vasquez`.
felix // Imprime el prototipo Persona con los datos del objeto felix: `Persona {nombre: “Felix”, apellido: “Vasquez”}`
Podemos crear varios objetos a partir de la función constructora:
var erika = new Persona (`Erika`, `Ruiz`)
var jessica = new Persona (`Jessica`, `Moreno`)
erika // Imprime: `Persona {nombre: “Erika”, apellido: “Ruiz”}`.
erika.saludar() // Imprime `Hola, me llamo Erika Ruiz`.
Reto de la clase: agrega el atributo altura y la función soyAlto e imprime “soy alto” o “soy bajito” de acuerdo a si mide más de 1.8mts.
var umbral = 1.8functionPersona(nombre, apellido, altura) {
this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
Persona.prototype.saludar = function () {
if (this.altura >= umbral) {
console.log(`Hola, me llamo ${this.nombre}${this.apellido} y soy alto`)
} else {
console.log(`Hola, me llamo ${this.nombre}${this.apellido} y soy bajito`)
}
}
var felix = new Persona(`Felix`, `Vasquez`, 1.8)
var jessica = new Persona(`Jessica`, `Moreno`, 1.6)
var erika = new Persona(`Erika`, `Ruiz`, 1.9)
Comentario de Dragonatack excelente complemento a la clase:
Clases en JS:
Al hablar de clases en JS, no se habla precisamente de ellas sino de prototipos, esto se debe a que no son clases como en otros lenguajes, ya que NO existe la herencia en JS .
La palabra new
se utiliza para crear nuevos objetos, dado un prototipo.
Al ejecutarse la función que lleva por nombre el prototipo, implícitamente va a retornar el objeto del prototipo que acaba de construir .
En esta clase veremos cómo se modifican las clases de herencias. JavaScript funciona con una estructura orientada a objetos y cada objeto tiene una propiedad privada que mantiene un enlace a otro objeto llamado prototipo.
En esta clase seguiremos aprendiendo sobre cómo funcionan los prototipos dentro de JS y que diferencia tienen con respecto al concepto de Herencia
de otros lenguajes de programación.
Es de mucha importancia donde colocamos las funciones que va a tener el prototipo. Por ello definimos los prototipos arriba, y los utilizamos a lo largo del código.
functionPersona(nombre, apellido) {
this.nombre = nombre
this.apellido = apellido
}
var felix = new Persona(`Felix`, `Vasquez`)
felix.saludar()
// Definir la función después de invocarla nos arrojará error.
Persona.prototype.saludar = function () {
console.log(`Hola, me llamo ${this.nombre}${this.apellido}`)
}
Si la función saludar() la declaramos después de la invocación de la misma el navegador arrojará error porque la función que se está invocando NO ESTÁ DEFINIDA. JavaScript detiene su ejecución en el momento que ocurre un error. Por eso nunca carga en memoria la función.
this
?En esta clase explicamos por qué al introducir el arrow function salió un error. El error del contexto de this
en JavaScript es uno de los errores más comunes.
Recuerda que dentro de la arrow function, this
está haciendo referencia al espacio global, a window
. (o quizás solo se refiere al nivel superior [el siguiente scope]). (Lexical this
, permite con this
llegar a un nivel superior de la función).
Ejercicio del profesor en clases:
functionPersona(nombre, apellido, altura) {
this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
Persona.prototype.saludar = function () {
console.log(`Hola, me llamo ${this.nombre}${this.apellido}`)
}
Persona.prototype.soyAlto = function () {
returnthis.altura > 1.8
}
var felix = new Persona(`Felix`, `Vasquez`, 1.9)
var jessica = new Persona(`Jessica`, `Moreno`, 1.6)
felix.soyAlto() // Imprime `true`
jessica.soyAlto() // Imprime `false`
Modificando las funciones con Arrow functions:
functionPersona(nombre, apellido) {
this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
Persona.prototype.saludar = function () => console.log(`Hola, me llamo ${this.nombre} ${this.apellido}`)
Persona.prototype.soyAlto = function () => this.altura > 1.8
}
varfelix = newPersona(`Felix`, `Vasquez`, 1.9)
varjessica = newPersona(`Jessica`, `Moreno`, 1.6)
felix.soyAlto() // Imprime `false` **¡¡¿¿WHAT??!!**
jessica.soyAlto() // Imprime `false`
Los objetos en JavaScript son “contenedores” dinámicos de propiedades. Estos objetos poseen un enlace a un objeto prototipo. Cuando intentamos acceder a la propiedad de un objeto, la propiedad no sólo se busca en el propio objeto sino también en el prototipo del objeto, en el prototipo del prototipo, y así sucesivamente hasta que se encuentre una propiedad que coincida con el nombre o se alcance el final de la cadena de prototipos.
Para esta clase continuaremos con el ejercicio anterior.
NOTA: es importante aclarar que esta clase hace referencia al PROTOTIPADO y a como se hacía la herencia antes de la solución que veremos en la próxima clase. Es más complejo, pero sin duda nos da un panorama de lo que ocurre en el método class
introducido más recientemente.
functionPersona(nombre, apellido, altura) {
this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
Persona.prototype.saludar = function () { // Método que pertenece a la función/prototipo `Persona`.console.log(`Hola, me llamo ${this.nombre}${this.apellido}`)
}
Persona.prototype.soyAlto = function () { // Método que pertenece a la función/prototipo `Persona`.returnthis.altura > 1.8
}
functionDesarrollador(nombre, apellido, altura) { // Esta es la función que vamos a ejecutar cada vez que agreguemos nuevos desarrolladores, que a su vez son personas.this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
Desarrollador.prototype.saludar = function () { // Método que pertenece solo a la función/prototipo `Desarrollador`.console.log(`Hola, me llamo ${this.nombre}${this.apellido} y soy desarrollador`)
}
¿Cómo hacemos para que el prototipo Desarrollador
“herede” los métodos del prototipo Persona
.?
Para lograrlo usaremos el siguiente hack:
function heredaDe(prototipoHijo, prototipoPadre) {}
.Desarrollador
ya que será la encargada de agregar los prototipos del padre
al hijo
.var fn = function () {}
al cuerpo de la función heredaDe()
. Una función vacía que no hace nada. Esta función le dice al prototipoHijo
quien es su padre. En algunos lados podemos encontrar noop
en lugar de fn
.heredaDe(Desarrollador, Persona)
justo después de la función constructora Desarrollador y antes de cualquier método. Si ejecutamos el método saludar
o cualquier otro antes de heredar los prototipos del padre entonces estaremos usando los prototipos del propio elemento.functionheredaDe(prototipoHijo, prototipoPadre) {
var fn = function () {}
fn.prototype = prototipoPadre.prototype // Asignamos al prototype de la función `fn` el prototipo del padre.
prototipoHijo.prototype = new fn // Asignamos al prototype del prototipoHijo un nuevo objeto a partir de la función `fn`. Básicamente `fn` actúa como intermediario para pasar los prototipos de un objeto a otro, y evitar pisar el prototype del padre.
prototipoHijo.prototype.constructor = prototipoHijo // Asignamos la función constructora del prototipoHijo. Hay que pisar la función constructora porque de lo contrario estaríamos llamando a la función constructora del padre (Persona). Pisamos el constructor de forma similar a como hacemos en CSS.
}
functionPersona(nombre, apellido, altura) {
this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
Persona.prototype.saludar = function () { // Método que pertenece a la función/prototipo `Persona`.console.log(`Hola, me llamo ${this.nombre}${this.apellido}`)
}
Persona.prototype.soyAlto = function () { // Método que pertenece a la función/prototipo `Persona`.returnthis.altura > 1.8
}
functionDesarrollador(nombre, apellido, altura) { // Esta es la función que vamos a ejecutar cada vez que agreguemos nuevos desarrolladores, que a su vez son personas.this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
heredaDe(Desarrollador, Persona) // Invocamos la función heredar para que el objeto Desarrollador herede los prototipos antes de pasarlo por el método que imprime el saludo de Desarrollador.
Desarrollador.prototype.saludar = function () { // Método que pertenece solo a la función/prototipo `Desarrollador`.// De esta forma estamos pisando el atributo/función `saludar` del objeto padre, de una forma similar a como sucede en CSS.console.log(`Hola, me llamo ${this.nombre}${this.apellido} y soy desarrollador`)
}
10:25 El resultado de lo anterior se aprecia al consultar el atributo prototipo de Persona y Desarrollador
Esta clase me ha llevado a reflexionar sobre el parecido de “pisar” propiedades en CSS. El orden que se obtiene al “heredar” prototipos concuerda con la ejecución en cascada de CSS. El profe explica cómo el navegador recorre los atributos de un objeto hijo hacia su padre buscando el método que se haya solicitado y que solo arroja error
al llegar al último y no encontrarlo. El método que definimos en nuestro constructor “pisa” al anterior. Esto se debe a que se convierte en el primero en ejecutarse cuando el navegador recorre los atributos buscando el método. Se dice que “pisa” al siguiente método declarado en la herencia (cascada) porque al encontrarlo lo ejecuta y ya deja de seguir buscando en el árbol.
¿Cómo hago para que un prototipo herede de otro?
JS no soporta la herencia porque no soporta las clases, no hay clases, hay prototipos a los que les vamos agregando métodos que reciben funciones, saben quien es ‘this’ y saben cómo ejecutarlas. Pero no existe un sistema como tal donde yo diga: ”este prototipo va a heredar de otro”.
Lo que sí existe es la herencia prototipal.
¿Cómo funciona?
Es posible crear prototipos ‘hijo’ que van a ser un subtipo de persona en este caso, por ejemplo un ‘desarrollador’.
Este ‘hijo’, cada vez que sea requerido buscará los métodos en sí mismo, luego si no lo encuentra, en su ‘padre’, ‘abuelo’ hasta llegar al prototipo base de todos los objetos que es ‘object’. Si ‘object’ no conoce ese método, recién ahí es que javaScript lanzará el error de que ese método no puede ejecutarse.
Sin duda es complejo al principio entender los conceptos de esta clase. Pero es una gran satisfacción, luego de experimentar un poco, entender lo que sucede de fondo y desvelar las entrañas del monstruo 😂.
Acá les dejo los experimentos que hice en esta clase que me permitieron comprender el fondo del asunto:
extends
se usa en declaraciones de clase o expresiones de clase para crear una clase que es hija de otra clase.constructor
es un método especial para crear e inicializar un objeto creado a partir de una clase.class
es una palabra reservada para especificar una definición de objetos, una especie de plantilla a usar para crear los obejtos. En esta plantilla se define cómo se crean los objetos de tipo Factura
, qué propiedades tienen, cuáles son de sólo lectura, etc. FuenteclassPersona{
constructor(nombre, apellido, altura) {
this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
saludar() { // No hace falta la palabra `function` para declarar la función.console.log(`Hola, me llamo ${this.nombre}${this.apellido}`)
}
soyAlto() { // No hace falta la palabra `function` para declarar la función.returnthis.altura > 1.8
}
}
classDesarrolladorextendsPersona{
constructor(nombre, apellido, altura) {
this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
saludar() {
console.log(`Hola, me llamo ${this.nombre}${this.apellido} y soy desarrollador`)
}
}
Al intentar crear un objeto a partir de la función Desarrollador
nos da el siguiente error:
Uncaught ReferenceError: Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor
Esto se debe a que para llamar al constructor de la clase padre Persona
debemos invocar super()
. A partir de esta declaración ya podemos invocar a this
dentro de la función. La razón es que ahora no estamos invocando new
que era el encargado de:
this
;this
si la función no devuelve un objeto.La función corregida quedaría así:
classPersona{
constructor(nombre, apellido, altura) {
this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
saludar() { // No hace falta la palabra `function` para declarar la función.console.log(`Hola, me llamo ${this.nombre}${this.apellido}`)
}
soyAlto() { // No hace falta la palabra `function` para declarar la función.returnthis.altura > 1.8
}
}
classDesarrolladorextendsPersona{
constructor(nombre, apellido, altura) {
super(nombre, apellido, altura)
}
saludar() {
console.log(`Hola, me llamo ${this.nombre}${this.apellido} y soy desarrollador`)
}
}
En JavaScript, los parámetros de funciones son por defecto undefined
. De todos modos, en algunas situaciones puede ser útil colocar un valor por defecto diferente que lo evalúe como verdadero.
classPersona{
constructor(nombre, apellido, altura) {
this.nombre = nombre
this.apellido = apellido
this.altura = altura
}
saludar(fn) { // Creamos el parámetro `fn` para dar una respuesta al saludoconsole.log(`Hola, me llamo ${this.nombre}${this.apellido}`)
// Una vez que nos salude queremos saber si hay que responderle:if (fn) { // Preguntamos si hemos recibido el parámetro `responderSaludo`. Si es cierto ejecutamos la función, de lo contrario no.
fn(this.nombre, this.apellido, false) // Toma lo nombre y apellido y define como `false` el parámetro `esDev`, ya que en la función constructora `Persona` esto debe ser falso. También se puede dejar vacío para que la función reciba `undefined`, o null, o cualquier otro valor `falsy`.
}
}
soyAlto() {
returnthis.altura > 1.8
}
}
classDesarrolladorextendsPersona{
constructor(nombre, apellido, altura) {
super(nombre, apellido, altura)
}
saludar(fn) {
console.log(`Hola, me llamo ${this.nombre}${this.apellido} y soy desarrollador/a`)
if (fn) { // Preguntamos si hemos recibido el parámetro `responderSaludo`. Si es cierto ejecutamos la función, de lo contrario no.
fn(this.nombre, this.apellido, true) // Toma lo nombre y apellido y define como `true` el parámetro `esDev`, ya que en la función constructora `Desarrollador` esto debe ser verdadero. También podemos usar cualquier valor truthy.
}
}
}
functionresponderSaludo(nombre, apellido, esDev) {
console.log(`Buen día ${nombre}${apellido}`)
if (esDev) {
console.log(`¡Ah mirá!, no sabía que eras desarrollador/a web`)
}
}
var felix = new Persona(`Felix`, `Vasquez`, 1.9)
var erika= new Persona(`Erika`, `Ruiz`, 1.8)
var jessica = new Desarrollador(`Jessica`, `Moreno`, 1.6)
felix.saludar() // No pasamos ningún parámetro. Imprime `Hola, me llamo Felix Vasquez`.
erika.saludar(responderSaludo) // Pasamos la función responder como parámetro. Imprime `Hola, me llamo Erika Ruiz` y `Buen día Erika Ruiz`
jessica.saludar(responderSaludo) // Pasamos la función responder como parámetro. Imprime `Hola, me llamo Jessica Moreno y soy desarrollador/a` y `Buen día Jessica Moreno` y `¡Ah mirá!, no sabía que eras desarrollador/a web`.
Durante el ejercicio anterior el profesor olvidó colocar this
en los elementos dentro de la función fn
al llamar a nombre
y apellido
. Esto arrojó un error en consola. Para solucionarlo pondremos en práctica lo aprendido en clases anteriores, vamos a desestructurar el objeto que pasamos a la función fn
de la siguiente manera:
saludar(fn) {
var nombre = this.nombre // Creamos la variable `nombre` y la igualamos al atributo `nombre` del objeto `this`.var apellido = this.apellido // Lo mismo con el apellido// La siguiente es la forma de acceder a los atributos desestructurando el objeto `this`. Ambas son válidas.// var ({ nombre, apellido }) = this console.log(`Hola, me llamo ${nombre}${apellido} y soy desarrollador/a`) // Y eliminamos el `this`.if (fn) {
fn(nombre, apellido, true) // Y eliminamos el `this`.
}
}
3:49 Valores falsy
(valores falsos) en Javascript:
Los demás son valores Truthy
(valores verdaderos).
Podemos ver más aquí: https://developer.mozilla.org/es/docs/Glossary/Truthy
Cuando una función no recibe un parámetro lo que se obtiene es un undefined
, y eso es falso
.
JavaScript sólo puede hacer una cosa a la vez, sin embargo; es capaz de delegar la ejecución de ciertas funciones a otros procesos. Este modelo de concurrencia se llama EventLoop.
JavaScript delega en el navegador ciertas tareas y les asocia funciones que deberán ser ejecutadas al ser completadas. Estas funciones se llaman callbacks, y una vez que el navegador ha regresado con la respuesta, el callback asociado pasa a la cola de tareas para ser ejecutado una vez que JavaScript haya terminado todas las instrucciones que están en la pila de ejecución.
Si se acumulan funciones en la cola de tareas y JavaScript se encuentra ejecutando procesos muy pesados, el EventLoop quedará bloqueado y esas funciones pudieran tardar demasiado en ejecutarse.
En principio, cualquier tarea que se haya delegado al navegador a través de un callback, deberá esperar hasta que todas las instrucciones del programa principal se hayan ejecutado. Por esta razón el tiempo de espera definido en funciones como setTimeout
, no garantizan que el callback se ejecute en ese tiempo exactamente, sino en cualquier momento a partir de allí, sólo cuando la cola de tareas se haya vaciado.
SWAPI.co ha desaparecido
Desafortunadamente, swapi.co ya no se mantiene y el servicio está actualmente inactivo. El autor del proyecto, Paul Hallett, quien creó y le dio mantenimiento ha desactivado desde hace tiempo esta API que muchos utilizamos en nuestros proyectos de JavaScript para aprender a integrar un backend a un frontend.
SWAPI.dev, una nueva solución
Juriy Bura, junto a otros desarrolladores, han publicado una versión idéntica a la API utilizada por Swapi.io, la cual está disponible desde el dominio swapi.dev. Por lo tanto, de ahora en adelante, para continuar el curso sin problemas solo debes reemplazar swapi.io, la URL obliterada (la que ya no funciona), por la nueva versión que sí está disponible: swapi.dev.
Este proyecto es mantenido por la comunidad y gracias a la filosofía del código libre es posible tener una nueva versión de la API para nuestros proyectos.
Crea tu propia API de Star Wars
¿Quieres tener tu propia versión de SWAPI?
El código fuente del proyecto está disponible en GitHub, lo que te permite crear tu propia versión con solo realizar un Fork y subirlo a un servidor o consumirla en tu localhost.
Este es el repositorio del proyecto original: https://github.com/phalt/swapi.
¡Compártenos en los comentarios en link a tu repositorio fork de la API de Star Wars y qué cambios hiciste o planeas hacer en tu versión de este proyecto!
En esta clase aprenderemos qué son los callbacks y usaremos una librería externa que se llama jQuery.
Un callback es una función que se pasa a otra función como un argumento. Esta función se invoca, después, dentro de la función externa para completar alguna acción.
const API_URL = `https://swapi.co/api/`const PEOPLE_URL = `people/:id`const lukeUrl = `${API_URL}${PEOPLE_URL.replace(`:id`, 1)}`const opts = { crossDomain: true }
const onPeopleResponse = function (persona) {
console.log(`Hola, yo soy ${persona.name}`)
}
$.get(lukeUrl, opts, onPeopleResponse)
En esta clase accederemos a múltiples datos al mismo tiempo. Continuaremos trabajando con los jQuery y swapi.
const API_URL = `https://swapi.co/api/`const PEOPLE_URL = `people/:id`const url = `${API_URL}${PEOPLE_URL.replace(`:id`, 1)}`const opts = { crossDomain: true } // Indica a JQuery que este request se va a hacer hacia otra página.const onPeopleResponse = function (persona) { // Función que llamaremos cuando regresemos del callback.console.log(`Hola, yo soy ${persona.name}`)
}
$.get(url, opts, onPeopleResponse) // Callback que consulta la api y llama a la función al obtener respuesta. Si no hay respuesta nunca llama a la función.
Mejorando el código y haciendo la función que invoque a la consulta al pasarle el parámetro id
con el personaje que queremos consultar.
const API_URL = `https://swapi.co/api/`const PEOPLE_URL = `people/:id`const opts = { crossDomain: true }
const onPeopleResponse = function (personaje) {
console.log(`Hola, yo soy ${persona.name}`)
}
functionobtenerPersonaje(id) {
const url = `${API_URL}${PEOPLE_URL.replace(`:id`, id)}`
$.get(url, opts, onPeopleResponse)
}
obtenerPersonaje(1) // Imprime `Hola yo soy C-3PO`.
obtenerPersonaje(2) // Imprime `Hola yo soy R2-D2`.
obtenerPersonaje(3) // Imprime `Hola yo soy Luke Skywalker`.
Lo que comprobamos con este ejercicio es que el orden en que fueron devueltos los personajes de star wars no fue el mismo orden en que fueron solicitados.
Una manera de asegurar que se respete la secuencia en que hemos realizado múltiples tareas es utilizando callbacks, con lo que se ejecutará luego, en cada llamada. Lo importante es que el llamado al callback se haga a través de una función anónima. Sin embargo, al hacerlo de esta manera generamos una situación poco deseada llamada CallbackHell.
const API_URL = `https://swapi.co/api/`const PEOPLE_URL = `people/:id`const opts = { crossDomain: true }
functionobtenerPersonaje(id, callback) { // Pasamos como parámetro la función que verifica si hay alguna función cómo parámetro. const url = `${API_URL}${PEOPLE_URL.replace(`:id`, id)}`
$.get(url, opts, function (persona) { // Movimos la función `onPeopleResponse` anónimamente console.log(`Hola, yo soy ${persona.name}`)
if (callback) { // Si el parámetro callback fue pasado a la función arroja verdadero
callback() // y ejecuta la función callback que pasamos como argumento en la función `obtenerPersonaje`.
}
})
}
obtenerPersonaje(1, function () {
obtenerPersonaje(2, function () {
obtenerPersonaje(3, function () {
obtenerPersonaje(4, function () {
obtenerPersonaje(5)
})
})
})
})
// Las llamadas se realizan en la secuencia indicada y las respuestas se reciben en el orden solicitado: // Imprime `Hola yo soy Luke Skywalker`.// Imprime `Hola yo soy R2-D2`.// Imprime `Hola yo soy C-3PO`.// Imprime `Hola yo soy Darth Vader`.// Imprime `Hola yo soy Leia Organa`.
El exceso de callbacks anidados se le conoce cómo CALLBACK HELL.
Para solucionar el problema de quedarnos sin conexión, u otro error similar, en medio de una sucesión de callbacks utilizamos el método fail()
.
const API_URL = `https://swapi.co/api/`const PEOPLE_URL = `people/:id`const opts = { crossDomain: true }
functionobtenerPersonaje(id, callback) {
const url = `${API_URL}${PEOPLE_URL.replace(`:id`, id)}`
$.get(url, opts, function (persona).fail(function () { // Encadenamos el método `fail()` para imprimir un error en caso que no se pueda terminar de ejecutar alguno de los callbacks.console.log(`Sucedió un error, No se pudo obtener el personaje ${id}`)`)
})
}
obtenerPersonaje(1, function (personaje) {
console.log(`Hola, yo soy ${personaje.name}`)
obtenerPersonaje(2, function () {
console.log(`Hola, yo soy ${personaje.name}`)
obtenerPersonaje(3, function () {
console.log(`Hola, yo soy ${personaje.name}`)
obtenerPersonaje(4, function () {
console.log(`Hola, yo soy ${personaje.name}`)
obtenerPersonaje(5)
console.log(`Hola, yo soy ${persona.name}`)
})
})
})
})
En esta clase veremos las promesas, que son valores que aún no conocemos. Las promesas tienen tres estados:
Las promesas se invocan de la siguiente forma:
newPromise( ( resolve, reject ) => { // Un objeto como cualquier otro de JS.// `resolve` y `reject` son funciones como parámetros que se ejecutan según la promesa se resuelve bien o mal.// --- llamado asíncronoif( todoOK ) {
// -- se ejecutó el llamado exitosamente
resolve()
} else {
// -- hubo un error en el llamado
reject()
}
} )
No olvides ver el material adjunto de esta clase: https://static.platzi.com/media/public/uploads/promesas_e7100aa0-540e-4d37-83fc-113b890c350e.pdf
1:06¿Qué son las Promesas?
Pensemos en ellas como valores que aún no conocemos. Es la promesa de que “ahí” habrá un valor cuando una acción asíncrona suceda y se resuelva.
const API_URL = `https://swapi.co/api/`const PEOPLE_URL = `people/:id`const opts = { crossDomain: true }
functionobtenerPersonaje(id) { // Ya no le pasamos la función callback.returnnewPromise((resolve, reject) => {
const url = `${API_URL}${PEOPLE_URL.replace(`:id`, id)}`
$
.get(url, opts, function (data) { // El callback ahora ejecuta la función `resolve()` con la respuesta obtenida de la consulta, es decir con el `personaje`.
resolve(data)
})
.fail(() => reject(id))
})
}
functiononError(id) {
console.log(`Sucedió un error, No se pudo obtener el personaje ${id}`)
}
obtenerPersonaje(1)
.then(function(personaje) {
console.log(`El personaje 1 es ${personaje.name}`)
})
.catch(onError) // Pasamos el nombre de la función sin los paréntesis // Imprime `El personaje 1 es Luke Skywalker`.
A diferencia de los callbacks en el CallbackHell, que terminan estando anidados unos dentro de otros, cuando se usan Promesas la ejecución de las llamadas no se hacen de manera anidada sino de manera encadenada, al mismo nivel una debajo de la otra, lo que hace que el código sea mucho más legible y mantenible.
Usando el ejemplo anterior:
const API_URL = `https://swapi.co/api/`const PEOPLE_URL = `people/:id`const opts = { crossDomain: true }
functionobtenerPersonaje(id) {
returnnewPromise((resolve, reject) => {
const url = `${API_URL}${PEOPLE_URL.replace(`:id`, id)}`
$
.get(url, opts, function (data) {
resolve(data)
})
.fail(() => reject(id))
})
}
functiononError(id) {
console.log(`Sucedió un error, No se pudo obtener el personaje ${id}`)
}
obtenerPersonaje(1)
.then(personaje => { // Cambiamos la función a Arrow function para disminuir la cantidad de texto.console.log(`El personaje 1 es ${personaje.name}`)
return obtenerPersonaje(2) // Esta función retorna el llamado a una función que retorna una Promesa. Hasta que no se resuelva esa promesa no pasamos al siguiente `.then`. Si algún fallo ocurre la función .catch captura el error, se detiene la ejecución de los siguientes `.then` y arroja el mensaje establecido en console.log().
})
.then(personaje => {
console.log(`El personaje 2 es ${personaje.name}`)
return obtenerPersonaje(3)
})
.then(personaje => {
console.log(`El personaje 3 es ${personaje.name}`)
return obtenerPersonaje(4)
})
.catch(onError) // Captura el error de cualquiera de las funciones `.then`. Si alguna falla este .catch captura.// Imprime `El personaje 1 es Luke Skywalker`.
Para hacer el llamado a múltiples promesas, nos apoyamos en un array de ids con el que luego construimos otro arreglo de Promesas, que pasaremos como parámetro a Promise.all( arregloDePromesas )
, con las promesas podemos encadenar llamadas en paralelo, algo que no es posible usando callbacks.
const API_URL = `https://swapi.co/api/`const PEOPLE_URL = `people/:id`const opts = { crossDomain: true }
functionobtenerPersonaje(id) {
returnnewPromise((resolve, reject) => {
const url = `${API_URL}${PEOPLE_URL.replace(`:id`, id)}`
$
.get(url, opts, function (data) {
resolve(data)
})
.fail(() => reject(id))
})
}
functiononError(id) {
console.log(`Sucedió un error, No se pudo obtener el personaje ${id}`)
}
var ids = [1, 2, 3, 4]
// var promesas = ids.map(function (id) {// return obtenerPersonaje(id) // })var promesas = ids.map(id => obtenerPersonaje(id)) // Forma más prolija de la función anterior.Promise.all(promesas) // Método que pertenece a la clase de promesas. Se le pasa un array de promesas.
.then(personajes => console.log(personajes))
.catch(onError)
// Imprime un array con 4 objetos. Cada objeto contiene los datos de la respuesta obtenida con el $.get// 0: {name: “Luke Skywalker”, height: “172”, mass: “77”, hair_color: “blond” ...}// 1: {name: “C-3PO”, height: “167”, mass: “75”, hair_color: “n/a” ...}// 2: {name: “R2-D2”, height: “96”, mass: “32”, hair_color: “n/a” ...}// 3: {name: “Darth Vader”, height: “202”, mass: “32”, hair_color: “none” ...}
Async-await es la manera más simple y clara de realizar tareas asíncronas. Await detiene la ejecución del programa hasta que todas las promesas sean resueltas. Para poder utilizar esta forma, hay que colocar async antes de la definición de la función, y encerrar el llamado a Promises.all() dentro de un bloque try … catch.
const API_URL = `https://swapi.co/api/`const PEOPLE_URL = `people/:id`const opts = { crossDomain: true }
functionobtenerPersonaje(id) {
returnnewPromise((resolve, reject) => {
const url = `${API_URL}${PEOPLE_URL.replace(`:id`, id)}`
$
.get(url, opts, function (data) {
resolve(data)
})
.fail(() => reject(id))
})
}
functiononError(id) {
console.log(`Sucedió un error, No se pudo obtener el personaje ${id}`)
}
asyncfunctionobtenerPersonajes() { // Para usar `await` debemos marcar la función con `async`.var ids = [1, 2, 3, 4]
var promesas = ids.map(id => obtenerPersonaje(id))
try { // Todo lo que deseamos ejecutar de forma asíncrona debemos incluirlo dentro de un bloque `try/catch`.var personajes = awaitPromise.all(promesas) // Cuando todas las promesas se resuelvan guarda el resultado en `promesas`.console.log(personajes)
} catch (id) { // pasamos el `id` para luego pasarlo a la función `onError`.
onError(id)
}
}
obtenerPersonajes()
// Imprime un array con 4 objetos. Cada objeto contiene los datos de la respuesta obtenida con el $.get// 0: {name: “Luke Skywalker”, height: “172”, mass: “77”, hair_color: “blond” ...}// 1: {name: “C-3PO”, height: “167”, mass: “75”, hair_color: “n/a” ...}// 2: {name: “R2-D2”, height: “96”, mass: “32”, hair_color: “n/a” ...}// 3: {name: “Darth Vader”, height: “202”, mass: “32”, hair_color: “none” ...}
Crearemos el juego Simón (Simón dice), en el que se van iluminando una secuencia de botones que el jugador tendrá que ir repitiendo, si se equivoca volverá a comenzar. El juego tendrá 10 niveles de dificultad, que deberán ser superados para ganar.
Trick:4:24 Truco para modificar varios textos iguales simultáneamente en VSCode. Seleccionas el texto y luego Ctrl+D.
// CREATE VARIABLES //const startButton = document.getElementById("startButton")
const lightBlue = document.getElementById("light-blue")
const purple = document.getElementById("purple")
const orange = document.getElementById("orange")
const green = document.getElementById("green")
// FUNCTION CONSTRUCTOR. This class is going to contain ALL the logic of the game //classGame{
constructor() {
this.initializer()
this.sequenceGenerator()
}
// Constructor`s Methods
initializer() {
startButton.classList.add("hide") // Hide the button after starting the game.this.level = 1// Create a method in the prototype called "level". this.colors = { // Create and object with all the colors used in game, call it "colors".1: lightBlue,
2: purple,
3: orange,
4: green,
}
}
}
// START GAME FUNCTION //functionstartGame() {
var game = new Game()
}
Para generar la secuencia del juego usaremos un array con números aleatorios, que representarán el color del botón que se iluminará cada vez. Usamos new Array()
para crear el arreglo de manera dinámica, y llamamos al método fill
para rellenar ese array con ceros y poder luego iterar sobre éste con map()
.
// CREATE VARIABLES //const startButton = document.getElementById("startButton")
const lightBlue = document.getElementById("light-blue")
const purple = document.getElementById("purple")
const orange = document.getElementById("orange")
const green = document.getElementById("green")
// FUNCTION CONSTRUCTOR //classGame{
constructor() {
this.initializer()
this.sequenceGenerator()
}
// Constructor`s Methods
initializer() {
startButton.classList.add("hide") // Hide the button after starting the game.this.level = 1// Create a method in the prototype called "level". this.colors = { // Create and object with all the colors used in game, call it "colors".1: lightBlue,
2: purple,
3: orange,
4: green,
}
}
sequenceGenerator() { // Creating the sequence of elements the user has to follow.this.sequence = newArray(10).fill(0).map(n => Math.floor(Math.random() * 4))
}
}
// START GAME FUNCTION //functionstartGame() {
var game = new Game()
}
Trick:3:43 para acceder al valor de una variable dentro de una función desde la consola podemos cambiar la declaración de la variable a window
(objeto global) de la siguiente manera:
// AntesfunctionempezarJuego() {
var juego = new Juego()
}
// DespuésfunctionempezarJuego() {
window.juego = new Juego()
}
// De esta manera podemos acceder a los valores de las variables declaradas dentro de las funciones.
En esta clase se observa la diferencia entre el uso de let
y var
para la declaración de variables y cómo esta diferencia afecta el alcance de la variable dentro de un ciclo for
. Se recomienda siempre el uso de let
cuando se trata de estructuras for
, ya que al usar var
, el valor de dicha variable se va a reemplazar cada vez con la última asignación que se haga, mientras que con let
, conservará su valor dentro de cada iteración.
Siempre que sea posible debemos usar const
sobre let
, y let
sobre var
.
// CREATE VARIABLES //const startButton = document.getElementById("startButton")
const lightBlue = document.getElementById("light-blue")
const purple = document.getElementById("purple")
const orange = document.getElementById("orange")
const green = document.getElementById("green")
// FUNCTION CONSTRUCTOR //classGame{
constructor() {
this.initializer()
this.sequenceGenerator()
this.nextLevel()
}
// Constructor`s Methods
initializer() {
startButton.classList.add("hide") // Hide the button after starting the game.this.level = 2// Create a method in the prototype called "level". this.colors = { // Create and object with all the colors used in game, called "colors".
lightBlue,
purple,
orange,
green,
}
}
sequenceGenerator() { // Creating the sequence of elements the user has to follow.this.sequence = newArray(10).fill(0).map(n => Math.floor(Math.random() * 4))
}
nextLevel() {
this.lightSequence()
// this.addClickEvents()
}
numberToColors(number) { // We pass the number through the switch to obtain the color match.switch (number) {
case0:
return"lightBlue"case1:
return"purple"case2:
return"orange"case3:
return"green"// Don`t need a `break`. It returns the case with the true value.
}
}
lightSequence() {
for (let i = 0; i < this.level; i++) {
// debuggerconst color = this.numberToColors(this.sequence[i]) // Create a variable with the color name.
setTimeout(() => this.lightColor(color), 1000 * i) // Illuminate the color by 1 sec. And for each interaction add 1 more sec.
}
}
lightColor(color) {
this.colors[color].classList.add("light") // Action to add a class with 0.5 opacity to the html element.
setTimeout(() => this.removeColor(color), 350) // Remove the class at 350ms.
}
removeColor(color) {
this.colors[color].classList.remove("light")
}
}
// START GAME FUNCTION //functionstartGame() {
//debuggerwindow.game = new Game()
}
Comentario relevante que explica con un ejemplo el problema de usar var
o let
en un ciclo for
: 1
Para obtener el input del usuario agregamos un manejador para el evento click del mouse usando addEventListener
para cada uno de los colores del juego. Utilizando la propiedad target devuelta por el evento click podemos identificar cuál es el botón que ha sido presionado.
Durante esta clase agregamos los siguientes códigos:
// Al initializer() le agregamos esta vinculación para evitar hacerla cada vez.this.chooseColor = this.chooseColor.bind(this)
// A la función nextLevel() agregamos el método adClickEvents.this.addClickEvents()
}
// Agregamos la escucha de los eventos que corresponde a cada div del juego.
addClickEvents() {
this.colors.lightBlue.addEventListener(`click`, this.chooseColor.bind(this))
this.colors.purple.addEventListener(`click`, this.chooseColor)
this.colors.orange.addEventListener(`click`, this.chooseColor)
this.colors.green.addEventListener(`click`, this.chooseColor)
}
chooseColor(ev) {
console.log(this)
}
Comentario de Filiberto explicando el this
y recomendando una lectura que él mismo hizo:this
es el botón porque this
en un addEventListener
representa al elemento HTML al cual le asignamos ese evento, por ese motivo pasa de ser Game()
a ser un elemento HTML.
Lo que hacemos con bind()
es decirle: ¡No!, tú no serás un elemento HTML, tú serás Game()
.
https://filisantillan.com/this-en-diferentes-situaciones-y-su-comportamiento/
Otro comentario interesante de Ulises:
Para entender This debemos de saber que this hace referencia le bloque de código que lo ejecuta, por lo que:
functionx (){
//this aquí hace referencia al bloque de código que es la función xfunctiony(){
//this aquí hace referencia al bloque de código que es la función y
}
}
Si queremos que this
haga referencia al bloque de código de la función x
debemos usar métodos como .bind(this)
4:11 Aquí el profesor explica el uso de .bind(this)
y el ¿por qué tenemos que usarlo?. Nos dice que podemos agregar al final del método:
addClickEvents() {
this.colors.lightBlue.addEventListener(`click`, this.chooseColor.bind(this))
// La desventaja es que esto habría que repetirlo por cada elemento que escuchemos.
}
chooseColor(ev) {
console.log(this)
}
Otra forma que podremos encontrar en muchos códigos es haciendo referencia a _this
o self
:
addClickEvents() {
var _this = thisvar self = this// Que equivale a lo mismothis.colors.lightBlue.addEventListener(`click`, this.chooseColor.bind(_this))
this.colors.lightBlue.addEventListener(`click`, this.chooseColor.bind(seft))
}
chooseColor(ev) {
console.log(this)
}
El objetivo con esto no perder la referencia del this
y obtener la función que construimos para el juego en vez del elemento html.
Para obtener el input del usuario agregamos un manejador para el evento click del mouse usando addEventListener
para cada uno de los colores del juego. Utilizando la propiedad target
devuelta por el evento click podemos identificar cuál es el botón que ha sido presionado.
Incluiremos una librería de mensajes con estilos mucho más agradables que el mensaje básico de javascript para mostrar los estados finales del juego al usuario.
Librería Sweet Alert
cdnjs
source of the library: https://cdnjs.cloudflare.com/ajax/libs/sweetalert/2.1.2/sweetalert.min.js
Introducimos la librería agregando el source al inicio de los <script>
y al final del <body>
.
<body></body><scriptsrc:”https://cdnjs.cloudflare.com/ajax/libs/sweetalert/2.1.2/sweetalert.min.js”></script>
Así quedó el código JS del juego Simón Dice:
// CREATE VARIABLES //const startButton = document.getElementById("startButton")
const lightBlue = document.getElementById("light-blue")
const purple = document.getElementById("purple")
const orange = document.getElementById("orange")
const green = document.getElementById("green")
const LAST_LEVEL = 2// FUNCTION CONSTRUCTOR //classGame{
constructor() {
this.initializer()
this.sequenceGenerator()
this.nextLevel()
}
// Constructor`s Methods
initializer() {
this.nextLevel = this.nextLevel.bind(this)
this.chooseColor = this.chooseColor.bind(this) // Use this to change what we get on chooseColor().this.toggleStartButton()
startButton.classList.add("hide") // Hide the button after starting the game.this.level = 1// Create a method in the prototype called "level". this.colors = { // Create and object with all the colors used in game, called "colors".
lightBlue,
purple,
orange,
green,
}
}
toggleStartButton() {
if (startButton.classList.contains("hide")) {
startButton.classList.remove("hide")
} else {
startButton.classList.add("hide")
}
}
sequenceGenerator() { // Creating the sequence of elements the user has to follow.this.sequence = newArray(LAST_LEVEL).fill(0).map(n => Math.floor(Math.random() * 4))
}
nextLevel() {
this.sublevel = 0// Creating this attribute to set an init level.this.lightSequence()
this.addClickEvents()
}
numberToColors(number) { // Method. We pass the number through the switch to obtain the color match.switch (number) {
case0:
return"lightBlue"case1:
return"purple"case2:
return"orange"case3:
return"green"// Don`t need a `break`. It returns the case with the true value.
}
}
colorToNumbers(color) { // Method. We pass the color through the switch to obtain the number match.switch (color) {
case"lightBlue":
return0case"purple":
return1case"orange":
return2case"green":
return3// Don`t need a `break`. It returns the case with the true value.
}
}
lightSequence() {
for (let i = 0; i < this.level; i++) {
// debuggerconst color = this.numberToColors(this.sequence[i]) // Create a variable with the color name.
setTimeout(() => this.lightColor(color), 1000 * i) // Illuminate the color by 1 sec. And for each interaction add 1 more sec.
}
}
lightColor(color) {
// debuggerthis.colors[color].classList.add("light") // Action to add a class with 0.5 opacity to the html element.
setTimeout(() => this.removeColor(color), 350) // Remove the class at 350ms.
}
removeColor(color) {
this.colors[color].classList.remove("light")
}
addClickEvents() {
this.colors.lightBlue.addEventListener(`click`, this.chooseColor) // We could use `this.chooseColor.bind(this)` this.colors.purple.addEventListener(`click`, this.chooseColor) // but we already declare it in initializer() to avoid to repeat it.this.colors.orange.addEventListener(`click`, this.chooseColor)
this.colors.green.addEventListener(`click`, this.chooseColor)
}
removeClickEvents() {
this.colors.lightBlue.removeEventListener(`click`, this.chooseColor) // We could use `this.chooseColor.bind(this)` this.colors.purple.removeEventListener(`click`, this.chooseColor) // but we already declare it in initializer() to avoid to repeat it.this.colors.orange.removeEventListener(`click`, this.chooseColor)
this.colors.green.removeEventListener(`click`, this.chooseColor)
}
chooseColor(ev) {
const colorName = ev.target.dataset.color; // Capturing the color the div we clicked.const colorNumber = this.colorToNumbers(colorName); // Transforming the color into a number.this.lightColor(colorName); // Illuminating the div when click it.if (colorNumber === this.sequence[this.sublevel]) { // Verifying if the div clicked it's the right one.this.sublevel++ // If right we level up.if (this.sublevel === this.level) { // Verifying if we reach the level.this.level++ // Level up the game.this.removeClickEvents()
// Could happen 2 things: Win the game or Level up.if (this.level === (LAST_LEVEL + 1)) { // Verifying if we reach the last level of the game.this.youWin() // You win method.
} else { // If we don't reach the final level, we
setTimeout(this.nextLevel, 1500); // We set bind(this), because if we don't, this become in window and lose the reference to the game.
}
}
} else {
this.youLoss() // You loss method.
}
}
youWin() {
swal("Simon say:", "You win", "success" ) // Swal return a promise. That's why we use .then next.
.then(() => {
this.initializer()
this.toggleStartButton()
})
}
youLoss() {
swal("Simon say:", "You suck", "error" )
.then(() => {
this.removeClickEvents()
this.initializer()
this.toggleStartButton()
})
}
}
// START GAME FUNCTION //functionstartGame() {
//debuggerwindow.game = new Game()
Una alternativa más prolija podemos encontrar aquí: https://github.com/alanzzant/SimonColors
Otra opción: https://jcarlorm.github.io/platzi-game/
var
es la manera más antigua de declarar variables. No es muy estricta en cuanto al alcance, ya que al declarar variables de esta forma, dichas variables podrán ser accedidas, e incluso modificadas, tanto dentro como fuera de los bloques internos en una función.
Con let
por otra parte, el alcance se reduce al bloque (las llaves) en el cual la variable fue declarada. Fuera de este bloque la variable no existe. Una vez declarada la variable con let, no se puede volver a declarar con en ninguna otra parte de la función.
const
al igual que let
se define en el contexto o alcance de un bloque, a diferencia de let y var, las variables definidas como constantes (const), ya no podrán ser modificadas ni declaradas nuevamente, en ninguna otra parte de la función o el contexto en el que ya existen.
La recomendación es reducir siempre al mínimo el alcance de nuestras variables, por lo que se debe usar let
en lugar de var
mientras sea posible.
La memorización es una técnica de programación que nos permite ahorrar cómputo o procesamiento en JavaScript, al ir almacenando el resultado invariable de una función para que no sea necesario volver a ejecutar todas las instrucciones de nuevo, cuando se vuelva a llamar con los mismos parámetros. Es similar a usar memoria cache.
En la siguiente función hacemos un llamado recursivo de la función para obtener el factural de un número n
. El problema con esta forma de hacerlo es que en la medida que llamamos a la función de forma recursiva vamos agregando más y más tareas al CallStack. Y el mayor problema es que cada vez que requiera el factorial de n
número voy a tener que realizar el calculo, aún cuando ya lo hayamos hecho en el pasado, ya que no lo guardamops en nigún lugar. Este tipo de operación se dice que es una operación costosa desde el punto de vista del uso de los recursos computacionales.
functionfactorial(n) {
if (n === 1) {
return1// Cierra el ciclo recursivo y comienza la resolución de cada n factorial en la cola del callstack.
} else {
debuggerreturn n * factorial(n - 1) // Llamado recursivo. Crea una cola en el Callstack con cada llamado.
}
}
Para optimizar lo anterior y crear un recurso en memoria que nos ahorre el calculo cada vez podemos hacer lo siguiente:
functionfactorial(n) {
if (!this.cache) {
this.cache = {} // Crea un objeto llamado `cache` vacío si no existe.
}
if (this.cache[n]) { // Si este valor existe (si ya existe en el objeto este valor), entoncesreturnthis.cache[n] // Retorna el valor del objeto cache con la `clave: valor` correspondiente. Y listo cierra el llamado. No se ejecuta ningún calculo de factorial porque ya existe en nuestro objeto.
}
if (n === 1) {
return1// Cuando n llega a 1 cierra el ciclo recursivo porque sale de la función y retorna 1. Ese 1 es entregado a la primera función en cola para su ejecución.
}
debuggerthis.cache[n] = n * factorial(n - 1) // factorial(n-1) recibe valor calculado por la función y multiplica por el valor de n. factorial(n-1) crea llamado recursivo. this.cache crea el nuevo atributo `clave: valor` en el objeto cache.returnthis.cache[n] // Retorna el valor calculado arriba. Este valor retornado es entregado como parámetro a la siguiente función en espera del callstack.
}
El último valor factorial en el callstack es el número 1, el siguiente a resolver es el 2, y así hasta llegar al valor n
introducido en la función.
Para comprender mejor este proceso introduciremos un valor n = 5
:
return1// factorial(n-1) retorna 1 desde la función if. Y ese valor es entregado como resultado para la siguiente función en la fila.// Ahora se ejecuta la siguiente función en espera del callstack, que tiene un valor de n=2 asignado.
cache[2] = 2 * (1) // Resuelve la multiplicación.return2// Retorna el resultado de la operación y lo pasa como parámetro a la siguiente función.
cache[3] = 3 * (2)
return6// Y lo pasa como parámetro a la siguiente función
cache[4] = 4 * (6)
return24
cache[5] = 5 * (24)
return120// Y lo imprime por cosola
Callstack
Con variables de tipo Date, se pueden realizar operaciones de suma y resta similares a las que se realizan con números. El resultado que se obtiene está en milisegundos, por lo que luego hay que hacer algunas operaciones adicionales para llevarlos a días, meses o años según queramos. También aplica para Horas, Minutos, Segundos y Milisegundos.
functiondiasEntreFechas(fecha1, fecha2) {
const undia = 1000 * 60 * 60 * 24// Establecemos el valor de un día multiplicando 1000mseg*60seg*60min*24oras.const diferencia = Math.abs(fecha1 - fecha2) // Devuelve el valor absoluto de la resta. El módulo. elimina el signo.returnMath.floor(diferencia / undia) // Divide y redondea hacia abajo el resultado.
}
// Creamos las dos fechas que vamos a restar:const hoy = newDate()
const nacimiento = newDate(1990, 7, 12)
Comentario: También existen métodos para obtener un valor de la fecha en específico. Estos métodos son:
getFullYear()
getMonth()
getDate()
getHours()
getMinutes()
getSeconds()
getMilliseconds()
getTime()
getDay()
Date.now()
La recursividad es un concepto muy importante en cualquier lenguaje de programación. Una función recursiva es básicamente aquella que se llama (o se ejecuta) a sí misma de forma controlada, hasta que sucede una condición base.
Nota: me parece que el ejemplo de la clase #48 Memoización da un ejemplo de recursividad más interesante.
functiondivisionEntera(dividendo, divisor) {
if (dividendo < divisor) {
return0
}
return1 + divisionEntera(dividendo - divisor, divisor)
}
Un closure, básicamente, es una función que recuerda el estado de las variables al momento de ser invocada, y conserva este estado a través de reiteradas ejecuciones. Un aspecto fundamental de los closures es que son funciones que retornan otras funciones.
functioncrearSaludo(finalDeFrase) {
returnfunction (nombre) {
console.log(`Hola, soy ${nombre}${finalDeFrase}`)
}
}
const saludoArgentino = crearSaludo(`ché`)
const saludoMexicano = crearSaludo(`guey`)
const saludoColombiano = crearSaludo(`parse`)
saludoArgentino(`Sacha`) // Imprime `Hola soy Sacha ché`
saludoMexicano(`Sacha`) // Imprime `Hola soy Sacha guey`
saludoColombiano(`Sacha`) // Imprime `Hola soy Sacha parse`
Las estructuras de datos inmutables forman parte de los principios de la Programación Funcional y nos permiten evitar tener efectos colaterales en los datos. En otras palabras, que hayan modificaciones en las variables sin nuestro consentimiento, produciendo comportamientos inesperados en el programa.
spread (...)
para evitar modificar el objeto original. Trabajar los nuevos objetos mientras los originales permanecen inmutables.side effect
o efecto de lado sobre los objetos que no que remos modificar.const felix= {
nombre: `Felix`,
apellido: `Vasquez`,
edad: 36
}
// const cumpleanos = persona => persona.edad++ // Esta forma modifica el objeto original.const cumpleanosInmutable = persona => ({
...persona,
edad: persona.edad +1
})
El contexto (o alcance) de una función es por lo general, window. Así que en ciertos casos, cuando intentamos referirnos a this
en alguna parte del código, es posible que tengamos un comportamiento inesperado, porque el contexto quizás no sea el que esperamos.
Existen al menos tres maneras de cambiar el contexto de una función:
const felix = {
nombre: `Felix`,
apellido: `Vasquez`
}
const jessica = {
nombre: `Jessica`,
apellido: `Moreno`
}
functionsaludar() {
console.log(`Hola. mi nombre es ${nombre}`)
}
const saludarAFelix = saludar.bind(felix) // Imprime `Hola. mi nombre es Felix`const saludarAJessica = saludar.bind(jessica) // Imprime `Hola. mi nombre es Jessica`
Cuando usamos this
en la función saludar()
esta hace referencia a window
, ya que es window
quien esta ejecutando la función saludar()
porque la tenemos definida en el scope global.
El punto y coma es opcional en JavaScript, excepto en algunos casos: