JavaScript Como Lenguaje Interpretado
JavaScript Como Lenguaje Interpretado
Para que las instrucciones sean buenas, quien las desarrolle debe saber
exactamente qué deben ilustrar, en qué orden deben realizar ciertas
acciones, en qué etapas es más fácil confundirse, etc. Por supuesto, deben
saber qué efecto se va a lograr al final.
A principios de los 90, todas las páginas web eran estáticas. Las cosas
cambiaron en 1995 cuando la corporación Netscape contrató a Brendan
Eich y le encargó que desarrollara un nuevo lenguaje para su producto, el
navegador web Netscape Navigator. El nuevo lenguaje se llamó LiveScript,
pero poco después se cambió su nombre a JavaScript. Su tarea principal
era agregar dinámicas a los sitios web, lo que permitiría, por ejemplo, una
interacción más compleja con el usuario. Y así comenzó la carrera de
JavaScript.
El uso de JavaScript en los sitios web, que con el tiempo se ha vuelto cada
vez más complejo y, a menudo, contiene una lógica muy sofisticada, se
denomina programación del lado del cliente. El código a ejecutar se carga
junto con la página en el navegador, del lado del usuario, y el intérprete
que forma parte del navegador web permite su ejecución.
Algo de ayuda aquí puede ser la ofuscación del código, que consiste en
transformar nuestro script listo en una forma un poco menos legible (por
ejemplo, generando nombres aleatorios cortos de variables y funciones,
eliminando los signos de final de línea, etc.), pero el simple hecho es que si
alguien quiere robar nuestro código JavaScript, es muy poco lo que
podemos hacer para detenerlo.
Por otro lado, JavaScript tiene muchas ventajas sobre otros lenguajes de
programación, y una de las más grandes es una comunidad muy activa y
solidaria. Es fácil encontrar soluciones a problemas comunes y encontrar
ayuda en general. Esto también significa que se desarrollan activamente
herramientas que funcionan con JavaScript.
Sin embargo, lo que es una ventaja para unos puede resultar una
desventaja para otros. Un ejemplo de esto es el manejo dinámico de los
tipos de datos en JavaScript. Consiste en que podemos almacenar datos de
cualquier tipo en una variable (una variable es un contenedor en el que
almacenamos los datos que utilizaremos).
Herramientas de desarrollo
Los entornos en línea, son sitios que actúan como un editor sencillo y un
entorno de ejecución. Todos ellos tienen conjuntos similares de
características. Tienen diferentes interfaces de usuario, pero en principio
se comportan de manera similar. Te permiten escribir código, ejecutarlo
con fines de prueba y, en la mayoría de los casos, compartirlo con otros
usuarios.
JSFiddle
CodePen
JsBin
Plunker
Editor de Código
WebStorm
Intérprete
Intérprete
Depurador
Los programas de computadora son bestias complicadas, miles o incluso
millones de líneas de código (pero tranquilo, comenzaremos con solo unas
pocas). Con tal complejidad y tamaño, es imposible producir código sin
errores. Algunos tipos de errores, especialmente los lógicos (formalmente,
el programa está escrito correctamente, pero probablemente inventamos la
solución incorrecta al problema), solo se pueden encontrar mientras el
programa se está ejecutando y, a menudo, solo en circunstancias
especiales. Es realmente difícil averiguar qué sucede exactamente dentro
de un programa que se ejecuta a la velocidad del rayo, y para esos
problemas existen los depuradores.
¿Por qué "¡Hola, Mundo!"? Durante casi 50 años, esta frase y sus
derivados han marcado a alguien aprendiendo un nuevo lenguaje de
programación, aunque es más una tradición que otra cosa. La frase se usó
hace mucho tiempo en un libro muy importante sobre el lenguaje C, pero
el texto en sí no es relevante ahora.
console.log("¡Hola, Mundo!");
¡Hola, Mundo!
<body>
</body>
Documento HTML
<!DOCTYPE html>
<html>
<head>
<title>Empty Page</title>
</head>
<body>
</body>
</html>
La etiqueta <script>
HTML es leído por el navegador línea por línea, y las etiquetas se ejecutan
justo en el momento en que el navegador analiza la
etiqueta <script> (analizar en lenguajes de programación significa un
análisis formal del código por parte de una máquina para comprender su
estructura). Generalmente las etiquetas <script> se insertan en el
encabezado de la página entre las etiquetas <head> y </head>, y podemos
insertar muchos de ellos en un archivo, por ejemplo, para incluir código
JavaScript de diferentes archivos. Este comportamiento se puede cambiar
para scripts externos señalados por el atributo "src" usando los
atributos "defer" o "async".
En CSS, podemos definir qué fuente se usará en toda la página, qué color
tendrá el fondo o si el cursor del mouse, cuando se mueve sobre la tabla,
debe cambiar de forma.
La etiqueta <script>
El código JavaScript que ejecutará el navegador en la página debe
adjuntarse al HTML utilizando la etiqueta <script>, y existen dos formas
de hacerlo. El código se puede incrustar directamente dentro de las
etiquetas <script> y </script>, pero esto solo se recomienda cuando el
código es corto. Otro enfoque es utilizar el atributo "src" para apuntar a un
archivo separado que contiene el código JavaScript. Esto es especialmente
cierto cuando se va a usar el mismo código en varias páginas, porque
repetir exactamente el mismo código muchas veces es una mala práctica,
ya que cualquier cambio debe aplicarse a todos los archivos; y además,
aumenta artificialmente el tamaño de la página. La extensión del archivo
JavaScript es .js.
HTML es leído por el navegador línea por línea, y las etiquetas se ejecutan
justo en el momento en que el navegador analiza la
etiqueta <script> (analizar en lenguajes de programación significa un
análisis formal del código por parte de una máquina para comprender su
estructura). Generalmente las etiquetas <script> se insertan en el
encabezado de la página entre las etiquetas <head> y </head>, y podemos
insertar muchos de ellos en un archivo, por ejemplo, para incluir código
JavaScript de diferentes archivos. Este comportamiento se puede cambiar
para scripts externos señalados por el atributo "src" usando los
atributos "defer" o "async".
En CSS, podemos definir qué fuente se usará en toda la página, qué color
tendrá el fondo o si el cursor del mouse, cuando se mueve sobre la tabla,
debe cambiar de forma.
o en el caso de macOS:
De acuerdo, tal vez finalmente podamos ejecutar algo....
<!DOCTYPE html>
<html>
<head>
<title>Empty Page</title>
<script src="main.js"></script>
</head>
<body>
</body>
</html>
Luego, en el mismo editor, crea otro archivo, esta vez llamado main.js (este
es el nombre que usamos en nuestro archivo html). Debe contener una
línea que has visto antes:
console.log("¡Hola, Mundo!");
<html>
<head></head>
<body></body>
</html>
Sección 1
Variables
Variables
Nombrado de variables
Declarando variables
var height;
console.log(height); // -> undefined
console.log(weight); // -> Uncaught ReferenceError: weight is not defined
let height;
console.log(height); // -> undefined
Una de las diferencias básicas en el uso de var y let es que let nos impide
declarar otra variable con el mismo nombre (se genera un error). El uso de
var le permite volver a declarar una variable, lo que puede generar errores
en la ejecución del programa.
var height;
var height;
console.log(height); // -> undefined
let height;
let height; // -> Uncaught SyntaxError: Identifier 'height' has already been
declared
console.log(height);
Así que usa let para declarar variables, aunque solo sea porque no quieres
volver a declarar accidentalmente una variable.
Inicializando variables
Veamos un ejemplo:
height = 180;
console.log(height); // -> 180
A primera vista, puedes ver que nos hemos olvidado de declarar la variable
height. La sintaxis de JavaScript original permitía tal negligencia, y en el
momento de la inicialización hizo esta declaración por nosotros. Parece
una solución bastante buena, pero desafortunadamente a veces puede
conducir a situaciones ambiguas y potencialmente erróneas (diremos
algunas palabras más al respecto mientras discutimos el alcance).
"use strict";
La frase "use strict"; debe colocarse al principio del código. Hará que el
intérprete se ocupe del resto del código utilizando el modo estricto, que es
el estándar moderno de JavaScript. Todos los demás ejemplos de nuestro
curso estarán preparados para funcionar en este modo de forma
predeterminada, incluso si "use strict"; no siempre aparezca al principio
del código.
Constantes
Alcance
Veamos un ejemplo:
let counter;
counter = 1;
console.log(counter); // -> 1
counter = counter + 1;
console.log(counter); // -> 2
let counter;
counter = 1;
{
console.log(counter); // -> 1
counter = counter + 1;
console.log(counter); // -> 2
Por cierto, toma en cuenta que el código dentro del bloque se ha movido a
la derecha. Esto se denomina indentación. Para un intérprete de
JavaScript, no importa en absoluto, pero definitivamente aumenta la
legibilidad del código, lo que permite a los lectores (incluso a tí) descubrir
rápidamente qué partes del código están dentro y cuáles están fuera del
bloque. Los editores de código suelen agregar sangrías en los lugares
correctos por sí mismos, pero es un buen hábito recordarlo y, si no
aparecen automáticamente, da formato al código a mano.
let y const
Como puedes ver, la variable info declarada en el bloque más interno solo
es visible dentro de él. La variable weight es visible tanto dentro del bloque
en el que fue declarada como dentro del bloque anidado en ella. Y la
variable global height es visible en todas partes.
Simple, ¿no?
var
Empecemos explicando qué son las funciones. A menudo sucede que una
determinada pieza de código, que realiza alguna tarea específica, se
utilizará muchas veces. Sí, puedes copiar este fragmento de código, todas
sus instrucciones, en cualquier lugar donde desees utilizarlo. Sin embargo,
esto sería muy ineficiente. En primer lugar, el tamaño de nuestro
programa crecería innecesariamente. En segundo lugar, si quisiéramos
hacer algunos cambios en este código, por ejemplo, para corregir algún
error, tendríamos que hacerlo en todos los lugares donde lo usamos.
Una solución simple a esto problema es una función. Una función es solo
una pieza de código separada que nombras, de la misma manera que
nombras una variable. Si deseas usarla en algún lugar, simplemente se
hace referencia a ella con ese nombre (decimos que llamamos a la función).
function testFunction() {
console.log("Hola");
console.log("Mundo");
}
Comencemos:
Hola
Mundi
y otra vez:
Hola
Mundo
y una vez más:
Hola
Mundo
Podemos reescribir el mismo programa usando nuestra
función testFunction. Declarémoslo de nuevo y llamémosla en los lugares
correctos:
function testFunction() {
console.log("Hola");
console.log("Mundo");
}
console.log("Comencemos:");
testFunction();
console.log("y otra vez:");
testFunction();
console.log("y una vez más:");
testFunction();
salida
El efecto del programa será el mismo que antes (prueba ambos ejemplos).
function testFunction() {
var localGreeting = "Días";
console.log("función:");
console.log(globalGreeting);
console.log(localGreeting);
}
testFunction();
console.log("programa principal:");
console.log(globalGreeting);
console.log(localGreeting); // -> Uncaught ReferenceError: localGreeting is
not defined
Sombreado
Sombreado - continuación
function testFunction() {
var counter = 200;
console.log(counter);
}
En la mayoría de los casos, esto no es deseable, así que trate de evitar dar
los mismos nombres de variable a varias variables, independientemente de
dónde las declares.
Hoisting
¿Recuerdas que dijimos que todas las variables deben declararse antes de
su uso? Esto no es del todo cierto, y realmente la palabra "debería" encaja
mejor que "debe". Por supuesto, una buena práctica es siempre declarar
las variables antes de usarlas. Y apégate a esto. Pero la sintaxis de
JavaScript original permite algunas desviaciones de esta regla.
Además, es más una curiosidad que algo práctico que usarás al escribir
programas, por lo que veremos solo un pequeño ejemplo que nos permitirá
comprender aproximadamente su principio. Esto puede facilitarte la
comprensión de algunas situaciones sorprendentes al escribir tu propio
código o probar ejemplos que encuentres en varias fuentes.
var weight;
var height = 180;
console.log(height); // -> 180
console.log(weight); // -> undefined
weight = 70;
console.log(weight); // -> 70
Sin embargo, no entraremos en eso. Basta con que seas consciente del
fenómeno. Y sobre todo, recordar SIEMPRE declarar las variables antes de
usarlas.
Resumen
Usar variables, es decir, declarar, inicializar, cambiar o leer sus valores es
una parte elemental de prácticamente todos los lenguajes de
programación. JavaScript no es la excepción, ya que necesitas usar
variables para programar en él. Recuerda declarar las variables antes de
usarlas. Presta atención en dónde las declaras, ya sean locales o globales.
Trata de usar las palabras clave let y const, no la palabra var. Saber esto
último será útil no solo para comprender los ejemplos que se encuentran
en varias fuentes, sino para que puedas evitar hacer lo mismo. Recuerda
no usar los mismos nombres para diferentes variables, incluso si las
declaras en diferentes rangos. Y, por supuesto, asigna nombres a las
variables que estarán relacionadas con lo que deseas almacenar en ellas:
el código debe ser legible no solo para el intérprete, sino también para las
personas.
Tareas
Tarea 1
el precio de una sola rosa (8) y el número de rosas que tienes (70)
el precio de un solo lirio (10) y el número de lirios que tienes (50)
el precio de un solo tulipán (2) y la cantidad de tulipanes que tienes
(120)
Ahora declara tres variables, una para cada una de las rosas, lirios y
tulipanes que tienes, en las que colocas su precio total. Inserta los valores
correspondientes en las variables utilizando las variables declaradas en el
paso anterior. Finalmente, declara una variable en la que almacenes el
precio de todas tus flores (nuevamente, usa las variables anteriores para la
inicialización). Muestra toda la información del inventario en la consola de
la siguiente forma:
Tarea 2
Modifica el código del ejemplo anterior. Supón que los precios de las flores
serán constantes (no cambiarán). Declara e inicializa las variables
restantes de la misma manera que en el ejemplo anterior. Muestra toda la
información recopilada en la consola. Ahora disminuye el número de rosas
en 20 y el de lirios en 30. Vuelve a mostrar toda la información recopilada
en la consola.
Distinguir los datos por sus tipos es uno de los rasgos característicos de
cualquier lenguaje de programación. Cada tipo de dato está conectado con
ciertas operaciones que podemos realizar sobre él. Por lo general, también
existen métodos para convertir datos entre tipos seleccionados (por
ejemplo, un número se puede convertir para que se guarde como una
cadena).
Veamos un ejemplo:
El operador typeof
"undefined"
"object"
"boolean"
"number"
"bigint"
"string"
"symbol"
"function"
salida
Boolean
continueLoop = true;
Number (numérico)
console.log(a); // -> 10
console.log(b); // -> 16
console.log(c); // -> 8
console.log(d); // -> 2
let x = 9e3;
let y = 123e-5;
console.log(x); // -> 9000
console.log(y); // -> 0.00123
let a = 1 / 0;
let b = -Infinity;
Prueba estos ejemplos e intenta cambiar los valores que aparecen en ellos
tu mismo.
BigInt
console.log(big2); // -> 1n
console.log(7n / 4n); // -> 1n
String (cadenas)
Las Cadenas, como otros datos primitivos, son inmutables, por lo que
cuando queremos cambiar incluso una letra en una cadena, en realidad,
creamos una nueva cadena.
Si usas comillas dobles para marcar una cadena, puedes colocar comillas
simples dentro de la cadena y se tratarán como caracteres normales. Esto
también funcionará en la situación opuesta (es decir, colocar comillas
dobles entre comillas simples).
Puedes hacer mucho trabajo útil con datos del tipo String.
Desafortunadamente, requieren dos nuevos conceptos: métodos (e
indirectamente, objetos) y autoboxing. La explicación exacta de ambos
conceptos va más allá del alcance de este curso, por lo que intentaremos
simplificarlos un poco.
console.time();
console.log("probar consola"); // -> probar consola
console.timeEnd(); // -> default: 0.108154296875 ms
Todos los datos de tipos primitivos como Number, BigInt, Boolean o String
tienen objetos correspondientes a los que se pueden convertir. Cada uno
de estos objetos tendrá métodos diseñados para un tipo de dato específico.
Llegados a este punto, llegamos a otro concepto, el autoboxing. Si aparece
un punto después de un literal que representa un tipo primitivo, o después
de una variable que contiene este tipo de datos, el intérprete de JavaScript
intenta tratar este valor como un objeto y no como un primitivo. Para este
propósito, convierte al dato primitivo en el objeto correspondiente sobre la
marcha, que tiene los métodos apropiados (es decir, realiza autoboxing).
Un poco confuso, ¿no? Afortunadamente, para usar métodos, no tenemos
que entenderlo exactamente, es suficiente seguir la convención dada.
Undefined
let declaredVar;
console.log(typeof declaredVar); // -> undefined
declaredVar = 5;
console.log(typeof declaredVar); // -> number
declaredVar = undefined;
console.log(typeof declaredVar); // -> undefined
Symbol
null
let someResource;
console.log(someResource); // -> undefined
console.log(typeof someResource); // -> undefined
someResource = null;
console.log(someResource); // -> null
console.log(typeof someResource); // -> object
console.log(str); // ->
console.log(num); // -> 0
Conversiones
Conversión a String
Las conversiones son las más fáciles de entender, ya que intentan cambiar
directamente el valor a una cadena, y esto se puede hacer para todos los
tipos de datos primitivos. Así que no hay sorpresas allí. Toma en cuenta
que en el ejemplo, utilizamos la técnica descrita recientemente de
la interpolación de cadenas de caracteres.
let nr = 42;
let strNr = String(nr);
console.log(`${typeof nr} : ${nr}`); // -> number : 42
console.log(`${typeof strNr} : ${strNr}`); // -> string : 42
let bl = true;
let strBl = String(bl);
console.log(`${typeof bl} : ${bl}`); // -> boolean : true
console.log(`${typeof strBl} : ${strBl}`); // -> string : true
let un = undefined;
let strUn = String(un);
console.log(`${typeof un} : ${un}`); // -> undefined : undefined
console.log(`${typeof strUn} : ${strUn}`); // -> string : undefined
let n = null;
let strN = String(n);
console.log(`${typeof n} : ${n}`); // -> object : null
console.log(`${typeof strN} : ${strN}`); // -> string : null
Conversión a Number
La conversión a un número no es tan obvia como la conversión a una
cadena. Funciona como se esperaba para cadenas que representan
números reales, como "14", "-72.134", o cadenas que representan números
en notación científica, como "2e3 ", o valores numéricos especiales
como "NaN" o "Infinity".
console.log(Number(42)); // -> 42
console.log(Number("11")); // -> 11
console.log(Number("0x11")); // -> 17
console.log(Number("0o11")); // -> 9
console.log(Number("0b11")); // -> 3
console.log(Number("12e3")); // -> 12000
console.log(Number("Infinity"));// -> Infinity
console.log(Number("text")); // -> NaN
console.log(Number(14n)); // -> 14
console.log(Number(123456789123456789123n)); // - > 123456789123
456800000
console.log(Number(true)); // -> 1
console.log(Number(false)); // -> 0
console.log(Number(null));// -> 0
Conversión a Boolean
0,
NaN,
cadena vacía,
undefined,
null
Conversión a BigInt
console.log(BigInt(true)); // -> 1n
Conversiones Implícitas
Tareas
Tarea 1
Solución
let b1 = true;
let b2 = Boolean(true);
let n1 = 100;
let n2 = Number(200);
let s1 = "Hello";
let s2 = String("Hello");
let u1 = undefined;
Tarea 2
Solución
Tarea 3
Ejemplo
// or
let s = "1234";
let n = Number(s);
let bi = BigInt(n);
let b = Boolean(bi);
Intenta agregar dos valores del mismo tipo y verifica el tipo de resultado.
Pruébalo para todos los tipos primitivos.
Ejemplo
Tarea 5
Ejemplos
Tarea 6
Ejemplo
Objeto
let testObj = {
nr: 600,
str: "texto"
};
¿Para qué necesitamos objetos? La razón más simple para usarlos puede
ser el deseo de almacenar varios valores en un solo lugar, que están
vinculados entre sí por alguna razón.
let user1 = {
name: "Calvin",
surname: "Hart",
age: 66,
email: "[email protected]"
};
let user2 = {
name: "Mateus",
surname: "Pinto",
age: 21,
email: "[email protected]"
};
console.log(user1.age); // -> 66
user1.age = 67;
console.log(user1.age); // -> 67
Arreglos
days[0] = "Sunday";
console.log(days[0]); // -> Sunday
animals[0] = "dog";
animals[2] = "cat";
let user1 = {
name: "Calvin",
surname: "Hart",
age: 66,
email: "[email protected]"
};
let user2 = {
name: "Mateus",
surname: "Pinto",
age: 21,
email: "[email protected]"
};
let users =[
{
name: "Calvin",
surname: "Hart",
age: 66,
email: "[email protected]"
},
{
name: "Mateus",
surname: "Pinto",
age: 21,
email: "[email protected]"
}
];
users[2] = {
name: "Irene",
surname: "Purnell",
age: 32,
email: "[email protected]"
Solo veremos algunos de ellos ahora, porque muchos otros requieren que
podamos crear nuestras propias funciones. Volveremos sobre algunos de
ellos en el apartado dedicado a las funciones.
length
console.log(names.length); // -> 4
names[5] = "Amelia";
console.log(names.length); // -> 6
console.log(names); // -> ["Olivia", "Emma", "Mateo", "Samuel", undefined,
"Amelia"]
indexOf
Toma en cuenta que hemos creado objetos usando el mismo literal, pero al
mismo tiempo hemos creado propiedades que son pares clave-valor. Las
propiedades están separadas por comas. Posteriormente, se puede hacer
referencia a una propiedad (campo) específica de un objeto con notación de
puntos. Esta notación requiere que el nombre del objeto (un literal o el
nombre de una variable que contiene el objeto) sea seguido por un punto,
seguido por el nombre del campo (clave) nuevamente.
push
names.push("Amelia");
console.log(names.length); // -> 5
console.log(names); // - > ["Olivia", "Emma", "Mateo","Samuel", "Amelia"]
unshift
pop
El método shift funciona de manera similar a pop, solo que esta vez
eliminamos el elemento del inicio del arreglo (con el índice 0). El método
devuelve el elemento eliminado y todos los demás elementos se desplazan
hacia la izquierda, llenando el espacio vacío. La longitud del arreglo
original se reduce en 1.
names.reverse();
console.log(names); // -> ["Samuel", "Mateo", "Emma", "Olivia"]
slice
let n2 = names.slice(1,3);
console.log(n2); // -> ["Emma", "Mateo"]
let n4 = names.slice(-1);
console.log(n4); // -> ["Samuel"]
concat
Resumen
Tareas
Objetos
Tarea 1
Ejemplo
let ticket = {
from: "Berlin",
to: "Potsdam",
price: 11
};
Task 2
Ejemplo
person.name = "Mary";
person.surname = "Stuart";
console.log(`${person.name} ${person.surname}`);
Arreglos
Tarea 3
Ejemplo
let books = [{
pages: 460
},
pages: 254
},
pages: 352
}
];
Tarea 4
Ejemplo
let newBook = {
pages: 254
};
books.push(newBook);
console.log(books.length);
console.log(books[0].title);
console.log(books[1].title);
console.log(books[2].title);
console.log(books[3].title);
Task 5
Utiliza el comando slice para copiar los dos últimos libros al nuevo
arreglo.
Ejemplo
let selectedBooks = books.slice(-2);
Tarea 6
Ejemplo
books.shift();
console.log(books.length);
console.log(books[0].title);
console.log(books[1].title);
console.log(books[2].title);
Tarea 7
Ejemplo
console.log(`pages: ${sum}`);
LABORATORIO
Tiempo estimado
15-30 minutos
Nivel de dificultad
Fácil
Objetivos
Escenario
let contacts = [{
}, {
email: "[email protected]"
}, {
email: "[email protected]"
}];
email: "[email protected]"};
contacts.push(newcontact);
console.log(contacts.length);
console.log(contacts[0].name);
console.log(contacts[0].phone);
console.log(contacts[0].email);
console.log(contacts[3].name);
console.log(contacts[3].phone);
console.log(contacts[3].email);
Comentarios
Los comentarios son solo texto sin formato, totalmente ignorados por el
intérprete de JavaScript, que generalmente sirven para explicar una
determinada pieza de código, que por algunas razones puede no ser
completamente legible. Sin embargo, no podemos escribirlos con total
libertad, ya que el intérprete intentará tratarlos como comandos, nombres
de variables o palabras clave. Entonces JavaScript necesita distinguir los
comentarios del resto del código. En JavaScript, tenemos dos tipos de
comentarios, y ambos se usan comúnmente en muchos lenguajes de
programación, desde la familia de lenguajes C hasta Python. Se
denominan comentarios de una sola línea y de varias líneas.
// x = 8;
console.log(x); // -> 42
Este método se usa con mayor frecuencia para "comentar" (es decir,
deshabilitar temporalmente) un fragmento de código seleccionado, por
ejemplo, para probar una versión alternativa del mismo.
/*
Este es un bloque
de comentarios y puede
abarcar varias líneas
Como regla general, los comentarios deben usarse cuando la lectura del
código no es suficiente para comprender lo que hace, o en situaciones en
las que el código se comporta de manera diferente a lo esperado y necesita
demostrar que es intencional. También debes recordar que en la mayoría
de los proyectos comerciales, no eres la única persona que leerá este
código. E incluso si lo fueras, leer tu propio código después de unos meses
es un desafío, y leer el código de otra persona puede llevar ese desafío al
siguiente nivel.
let result = a / b;
let result = a / b;
// dividir a entre b
let result = a / b;
Documentación
Alternar código
Tarea
Revisar
"use strict";
const prefix = "username_";
let prefixedUserName;
// const prefixedUserName;
userName = "John";
// console.log(prefixedUserName2);
Operadores
Operadores de asignación
Operadores aritméticos
Los operadores aritméticos expresan operaciones matemáticas y aceptan
valores numéricos y variables. Todos los operadores aritméticos, excepto la
suma, intentarán convertir implícitamente los valores al tipo Number
antes de realizar la operación.
const x = 5;
const y = 2;
console.log(n1++);
es interpretada como:
console.log(n1);
n1 = n1 + 1;
console.log(++n1);
let n1 = 10;
let n2 = 10;
console.log(n1); // -> 10
console.log(n1++); // -> 10
console.log(n1); // -> 11
console.log(n2); // -> 10
console.log(++n2); // -> 11
console.log(n2); // -> 11
let n3 = 20;
let n4 = 20;
console.log(n3); // -> 20
console.log(n3--); // -> 20
console.log(n3); // -> 19
console.log(n4); // -> 20
console.log(--n4); // -> 19
console.log(n4); // -> 19
x += 100;
x = x + 100;
let x = 10;
x += 2;
console.log(x); // -> 12
x -= 4;
console.log(x); // -> 8
x *= 3;
console.log(x); // -> 24
x /= 6;
console.log(x); // -> 4
x **= 3;
console.log(x); // -> 64
x %= 10;
console.log(x); // -> 4
Operadores Lógicos
Del mismo modo, funcionará al revés, cambiando una frase falsa por una
verdadera. En el código, se verá aún más simple:
const a = false;
const b = true;
const c = false;
const d = true;
Mientras los operandos sean del tipo Boolean, podemos ver fácilmente lo
que se devolverá. Pero esos operadores también se pueden usar con
diferentes tipos de datos. El caso más fácil es el NO lógico. Primero, el
operando se convierte temporalmente a un valor booleano (según las reglas
explicadas en el capítulo anterior) y solo entonces se trata con la acción de
operador adecuada (es decir, un valor verdadero se convierte en falso y
viceversa). Por lo tanto, el operador NOT siempre devolverá falso o
verdadero. A menudo, se utiliza la doble negación para convertir cualquier
tipo a Boolean.
let nr = 0;
let year = 1970;
let name = "Alice";
let empty = "";
let x = 0;
let y = 0;
console.log(x++ && y++); // -> 0
console.log(x); // -> 1
console.log(y); // -> y == 0
Debería ser fácil imaginar cómo funcionan. En el caso del operador AND,
podemos comprobarlo con el siguiente ejemplo:
let a = true;
console.log(a); // -> true
a &&= false;
console.log(a); // -> false
let b = false;
console.log(b); // -> false
b ||= true;
console.log(b); // -> true
Tareas
Tarea 1
Operadores aritméticos
Completa los operadores que faltan para obtener el resultado esperado (reemplaza el guión
bajo con el operador apropiado):
console.log(2 * 3 + 1);
console.log(2 ** 4);
console.log(5 * 1);
console.log(8 ** 2 - 5 ** 2);
Tarea 2
Operadores de comparación
Completa los operadores de comparación que faltan de tal manera que todas las expresiones
resulten true - verdaderas (reemplaza el guión bajo con el operador apropiado):
console.log(4 * 5 _ 20);
console.log(6 * 5 _ "30");
console.log(-17 _ 0);
console.log(25 _ 1);
console.log(2 + 2 * 2 _ 4);
Ejemplo
console.log(6 * 5 == "30");
console.log(2 + 2 * 2 != 4);
Tarea 3
Operadores Lógicos
Completa los operadores de comparación que faltan de tal manera que todas las expresiones
resulten true - verdaderas (reemplaza el guión bajo con el operador apropiado):
console.log(true _ false);
console.log(false _ false);
console.log(false _ false _ true);
console.log(true _ false _ false && true);
Ejemplo
console.log(true || false);
console.log(false || ! false);
Operadores de cadenas
El único operador en este grupo es la concatenación + . Este operador convertirá todo a una cadena si
alguno de los operandos es de tipo String. Finalmente, creará una nueva cadena de caracteres,
adjuntando el operando derecho al final del operando izquierdo.
Probablemente puedas adivinar que este operador también se puede usar junto con el operador de
reemplazo. Su funcionamiento es tan intuitivo que solo verémos un ejemplo sencillo:
sentence += 10191;
console.log(sentence); // -> Happy New Year 10191
Operadores de comparación
Los operadores de comparación se utilizan para verificar la igualdad o desigualdad de valores. Todos los
operadores de comparación son binarios y todos devuelven un valor lógico que representa el
resultado de la comparación, true o false.
Al igual que con otros operadores, JavaScript intentará convertir los valores que se comparan si son de
diferentes tipos. Tiene sentido verificar la igualdad, o cuál es mayor, usando la representación numérica,
y JavaScript en la mayoría de los casos convertirá los tipos a Number antes de la comparación. Hay dos
excepciones a esto, las cadenas y el operador de identidad (igualdad estricta). Las cadenas se
comparan char por char (especificamente carácter Unicode por carácter Unicode usando sus valores).
Para verificar si los operandos son iguales, podemos usar el operador de identidad (igualdad
estricta) === o el operador de igualdad <código style="box-sizing: border-box;">==</código>.
El primero es más restrictivo y, para devolver verdadero, los operandos deben ser idénticos (es decir,
deben ser iguales y del mismo tipo).
El operador de igualdad requiere que solo los valores sean iguales y sus tipos no se comparan. Entonces,
si los operandos son de diferentes tipos, el intérprete intentará convertirlos en números, por
ejemplo, true se convertirá en 0, true a 1, indefinido a NaN, null a 0, 10n
a 10 y "123" a 123, etc.
Toma en cuenta que si alguno de los operandos tiene un valor NaN (o se ha convertido a NaN, por
ejemplo, con undefined), el operador de igualdad devolverá false.
Otros operadores
La lista de operadores en JavaScript es mucho más larga, pero muchos de ellos no serían particularmente
útiles en esta etapa de aprendizaje, como los operadores bit a bit, que operan en bits únicos de
operandos. Sin embargo, vale la pena mencionar algunos operadores más, algunos de los cuales ya
aparecieron en ejemplos anteriores.
typeof
Ya presentamos el operador typeof cuando discutimos los tipos de datos. Es un operador unario, que
comprueba el tipo de operando (puede ser una variable o un literal). El operador devuelve una cadena
con el nombre del tipo, como "boolean" o "number".
Si deseas refrescar tus conocimientos sobre este operador, vuelve a la sección sobre tipos de datos.
El operador instanceof apareció mientras discutíamos los arreglos. Es un operador binario que
verifica si un objeto (operando izquierdo) es del tipo (operando derecho). Dependiendo del resultado de
la prueba, devuelve verdadero o falso.
Durante este curso, la utilidad de este operador se limita a probar si una variable contiene un arreglo.
delete
El operador unario delete se introdujo al hablar de los objetos. Te permite eliminar un campo
seleccionado del objeto cuyo nombre se indica con un operando.
let user = {
name: "Alice",
age: 38
};
console.log(user.age); // -> 38
delete user.age;
El último de los operadores discutidos es bastante inusual, porque es el único operador que usa tres
operandos. Es un operador condicional. Según el valor del primer operando (verdadero o falso), se
devuelve el valor del segundo o tercer operando, respectivamente. Este operador se usa con mayor
frecuencia para colocar uno de los dos valores en la variable dependiendo de una determinada condición.
Volveremos al operador cuando hablemos del condicional if, pero aquí daremos solo un ejemplo simple
de su uso. Los tres operandos están separados entre sí por ? (el primero del segundo) y : (el segundo del
tercero).
Cada uno de estos operandos puede ser una expresión que debe calcularse. En el siguiente ejemplo, el
primer operando es una comparación de dos números usando un operador de comparación. El resultado
de la comparación será falso, que será utilizado por el operador condicional (ternary). Aquí llegamos a
un problema importante sobre la precedencia de los operadores y el orden de ejecución. En un momento,
diremos algunas palabras más al respecto.
Precedencia
Prácticamente en todos los ejemplos donde presentamos la operación de operadores sucesivos, seguimos
instrucciones en las que se usaba un operador. En realidad, por lo general se utilizan múltiples
operadores simultáneamente. En este punto surge una pregunta bastante importante: ¿en qué orden las
realizará el intérprete? Por supuesto, esto afectará el resultado final de los operadores, por lo que vale la
pena tener esto en cuenta al escribir las instrucciones.
let a = 10;
let b = a + 2 * 3;
let c = a + 2 < 20 - 15;
console.log(a); // -> 10
console.log(b); // -> 16
console.log(c); // -> false
En la segunda línea del ejemplo (declaración de la variable b), los operadores se ejecutan en el orden que
conocemos de las matemáticas. Primero se realiza la multiplicación, luego la suma y al final se le asigna
el valor resultante a la variable. En la tercera línea (declaración de la variable c) el asunto se complica un
poco más. Primero se calcula la suma de la variable a y el número 2, luego la suma de los números 20 y
15, y ambos resultados se comparan con el operador lógico (menor que) y se coloca el resultado en la
variable c.
El intérprete de JavaScript utiliza dos propiedades de operador para determinar la secuencia de
operaciones: precedencia y asociatividad. La precedencia se puede tratar como una prioridad, con
algunos operadores que tienen la misma precedencia (por ejemplo, suma y resta). La asociatividad le
permite especificar el orden de ejecución si hay varios operadores con las mismas prioridades uno al
lado del otro.
La precedencia se puede presentar como un valor numérico: cuanto mayor sea el valor, mayor será la
prioridad de la operación. Si, por ejemplo, un operador OP1 tiene una precedencia menor que OP2 ,
entonces la instrucción:
a OP1 b OP2 c
se ejecutará de la siguiente manera: primero, OP2 se ejecutará en los operandos b y c, entonces OP1 se
ejecutará en el operando izquierdo a y el operando derecho, resultante de OP2 . Entonces la instrucción
podría presentarse en la forma:
a OP1 ( b OP2 c)
Si realizamos las mismas operaciones (u operaciones diferentes pero con la misma precedencia), el
intérprete utiliza la asociatividad para determinar el orden de las operaciones. Los operadores pueden
tener una asociatividad izquierda específica (orden de izquierda a derecha) o asociatividad derecha
(orden de derecha a izquierda). Supongamos que en nuestro ejemplo, el operador OP1 tiene
asociatividad por la izquierda:
a OP1 b OP2 c
En tal situación, la operación OP1 en los operandos a y b se realizará primero, seguida de una segunda
operación OP1 sobre el resultado recibido y el operando c. Teniendo en cuenta que se trata de
asociatividad por la izquierda, podríamos escribir la instrucción de la siguiente forma:
(a OP1 b) OP2 c
Se deduce que sería apropiado conocer no solo la precedencia de todos los operadores, sino también su
asociatividad. Esto puede parecer un poco abrumador, teniendo en cuenta la cantidad de operadores.
Afortunadamente, suele ser suficiente conocer las propiedades de los operadores más básicos y usar
paréntesis en situaciones dudosas. Los paréntesis te permiten imponer el orden de las operaciones, como
en matemáticas. Toma esto en cuenta cuando veas la tabla en la siguiente página. Contiene una lista de
operadores que ya conocemos con su precedencia y asociatividad, por lo que es bastante grande.
Absolutamente no tienes que recordar todo si puedes usar paréntesis para agrupar operaciones.
Precedencia - continuación
Precedencia Operador Asociatividad Símbolo
Negación unaria ⇐ -…
11 Incremento prefijo ⇐ ++ …
Decremento prefijo ⇐ -- …
Eliminar ⇐ delete …
9 Exponenciación ⇐ … ** …
Multiplicación ⇒ …*…
8 División ⇒ …/…
Residuo ⇒ …%…
Suma ⇒ …+…
7
Resta ⇒ …-…
instancia de ⇒ … instanceof …
5 Igualdad ⇒ … == …
Desigualdad ⇒ … != …
3 OR (O) lógico ⇒ … || …
…=…
… += …
1 Asignación ⇐ … *= …
Una flecha en la columna de asociatividad que apunta hacia el lado derecho significa asociatividad de
izquierda a derecha, mientras que hacia el lado opuesto significa asociatividad de derecha a izquierda.
La abreviatura n/a significa no aplicable, porque en algunos operadores el término asociatividad no tiene
sentido.
Al principio de la tabla, hay tres operadores que pueden necesitar más explicación:
Agrupar es simplemente usar paréntesis. Tienen prioridad sobre los demás operadores, por lo
que podemos usarlos para forzar la ejecución de operaciones para que tengan prioridad;
Acceso al campo (acceso al miembro) es el operador que se usa en la notación de puntos, que
es cuando se accede a un campo de objeto seleccionado. Tiene prioridad sobre otros operadores
(excepto los paréntesis), por ejemplo, la instrucción:
let x = myObject.test + 10; significa que el valor del campo test del
objeto myObject se obtendrá primero, luego le sumaremos un valor de 10, y el resultado irá a
la variable x;
La precedencia llamada de función nos dice que si llamamos a una función, esta acción tendrá
prioridad sobre otras operaciones, excepto la agrupación entre paréntesis y el operador de
acceso al campo (puntos). Así que en el ejemplo:
let a, b;
console.log(a); // -> 80
El uso de paréntesis no solo fuerza el orden de las acciones, sino que también aumenta la legibilidad del
código (la persona que lo lee no tiene que preguntarse qué y en qué orden se hará).
Resumen de Sección
En este capítulo, hemos introducido algunos operadores nuevos (por ejemplo, operadores
lógicos) y también hemos consolidado nuestro conocimiento sobre algunos que ya conocemos
(por ejemplo, operadores de asignación). Junto con los operadores, han aparecido nuevos
términos que describen sus propiedades: precedencia y asociatividad.
Afortunadamente, tenemos paréntesis para ayudar, lo que nos facilita forzar la prioridad de las
operaciones.
Los operadores aparecerán en todos los ejemplos hasta el final del curso, por lo que poco a
poco irás consolidando los conocimientos adquiridos, que seguramente se convertirán en un
todo lógico y coherente con el tiempo.
En el caso de JavaScript, la interacción con el usuario es un tema bastante complejo. Esto es por varias
razones. ¿Recuerdas que podemos escribir programas en este lenguaje tanto para usar en el navegador
(lado del cliente) como para realizar ciertas acciones en el lado del servidor? Esta división influye en la
interacción potencial. Los programas de JavaScript escritos con node.js para servidores generalmente no
requieren tal interacción. Los ejecutamos desde el nivel de la consola, a menudo sin un entorno gráfico.
La mayoría de las veces, el usuario da ciertas opciones (por ejemplo, datos de ruta) al programa que se
ejecuta de esta manera en forma de archivo de configuración y/o como llamada de argumentos. Los
datos para ejecutar el programa se toman luego de archivos de disco, bases de datos, servicios de red,
etc. Es muy raro que estos programas de consola necesiten ingresar algunos datos mientras se ejecuta el
programa. Si es necesario, se indica mediante la aparición de información apropiada en la consola, en la
que debes ingresar algunos datos.
Esto es definitivamente diferente para las aplicaciones del lado del cliente. En la mayoría de los casos,
requieren una interacción continua entre el usuario y el programa. Utilizándolos, podemos hacer clic en
botones, ingresar valores en formularios, seleccionar datos de listas desplegables, etc. El problema es
que prácticamente todos los elementos que se utilizan para este fin son componentes HTML. Usarlos
puede no ser muy difícil, pero requiere al menos una comprensión profunda de los conceptos básicos del
DOM (Document Object Model) que se utiliza en las páginas web y los conceptos básicos del propio
HTML
A continuación se muestra un ejemplo de una página HTML que utiliza dos elementos para la
interacción, para lo cual se utiliza código JavaScript:
<!DOCTYPE html>
<html>
<head></head>
<body>
<input id="myId" type="text"></input>
<button
onclick="console.log(document.getElementById('myId').value)">Obtener
Texto</button>
</body>
</html>
Ejecuta este código en el editor, asegurándote de que esté en la ventana dedicada a HTML (pestaña
index.html). El elemento <input> es un campo de entrada donde puedes ingresar cualquier texto. En
nuestro caso, le hemos dado a este elemento el identificador myId. El elemento <button>, como
puedes adivinar, corresponde a... un botón. Usando el atributo onClick, hemos indicado que si se hace
clic en el botón, se ejecutará una parte del código JavaScript. En este código, nos referimos al objeto del
documento (un fragmento del modelo DOM), que representa nuestro sitio web. Buscamos el elemento
con el identificador myId, recuperamos su valor (es decir, el texto ingresado) e imprimimos el resultado
en la consola.
Como puedes ver, la idea es relativamente simple, pero tanto el modelo DOM como el lenguaje HTML
están definitivamente más allá del alcance del curso actual. Entonces, ¿cómo podemos comunicarnos
con el usuario si no utilizamos estos mecanismos estándar?
Hay otra solución. Toma en cuenta que no se usa en las aplicaciones web modernas, pero te permite
brindar fácilmente al usuario la oportunidad de ingresar datos o tomar ciertas decisiones. Lo usaremos en
este curso para la comunicación normal, en lugar de como un elemento que encontrarás útil en la
programación real. La solución es usar cuadros de diálogo.
Cuadros de diálogo
Los cuadros de diálogo son partes integrales de los navegadores web y están disponibles en
casi todos ellos, incluso en los más antiguos. Todos ellos son ventanas emergentes (o
ventanas modales), lo que significa que cuando se muestra el cuadro de diálogo, no es
posible interactuar con la página web en sí hasta que se cierra este cuadro de diálogo.
Este inconveniente cuando la ventana emergente está visible es una de las razones por las
que no debemos abusar de ellos. Están perfectamente bien para el proceso de aprendizaje, y
en algunos casos excepcionales en los que se debe mostrar información importante o es
obligatoria alguna entrada del usuario, pero se deben evitar en otras circunstancias. Tenemos
tres cuadros de diálogo disponibles para usar.
El cuadro de diálogo de alerta es el más simple de los tres, y para mostrar un cuadro de
alerta, necesitamos llamar a un método llamado alert() . El método alert acepta un
parámetro opcional: el texto que se mostrará. El método alert es un método de la ventana
de objetos, pero por conveniencia, se puede usar sin necesidad de escribir window.alert ,
por lo que ambas formas son correctas y se pueden ver en aplicaciones reales. El objeto de
ventana es una generalización de la ventana o pestaña del navegador, y le da al desarrollador
acceso a datos relacionados con el estado de esta ventana (por ejemplo, cuánto se desplaza
hacia abajo la página, porque el objeto de consola es parte del objeto de ventana) y también
algunos métodos para controlar este estado.
alert("¡Hola, Mundo!")
alert(4 * 7);
alert(true);
La ventana de alert estará visible hasta que el usuario haga clic en el botón Aceptar visible
en la ventana emergente. La ejecución del código se detendrá hasta que se cierre el cuadro
de diálogo.
Los valores verdadero o falso, devueltos por el método confirm como resultado de la decisión del
usuario, permiten la ejecución condicional de algunas acciones del programa. En el siguiente ejemplo,
también hemos utilizado un operador condicional aprendido recientemente:
console.log(message);
Al igual que con otros cuadros de diálogo, el prompt acepta un parámetro opcional como un
mensaje que se mostrará. El prompt también acepta un segundo parámetro opcional, que es
el valor predeterminado del campo de texto visible en la ventana de diálogo. Al igual
que confirm, el método prompt devolverá un resultado que depende de la entrada del
usuario.
Si el usuario cierra la ventana con el botón Aceptar, todo lo que se encuentre en el campo de
texto se devolverá desde el método prompt como una cadena. Si el cuadro de diálogo se
cierra con el botón Cancelar, el método devolverá un valor null. Debido a que al pulsar el
botón Aceptar el valor devuelto será de tipo String, en ocasiones necesitaremos convertirlo a
otro tipo, por ejemplo, a un tipo Number. Al igual que con todas las entradas de los usuarios,
debemos estar preparados para el hecho de que los datos proporcionados por el usuario
pueden no ser válidos, ya sea por error o a propósito, por lo que siempre trata los valores
ingresados con precaución.
Resumen de Sección
Los cuadros de diálogo pueden no ser la forma más efectiva y elegante de comunicarse con el
programa, pero serán completamente suficientes para nuestras necesidades. Te permitirán
recuperar datos y tener en cuenta las decisiones del usuario, que podrán influir en el
programa.
Tareas
Tarea 1
Usando todo lo que has aprendido hasta este punto, escribe una secuencia de comandos que
le pregunte al usuario sobre el ancho, alto y largo de una caja, luego calcula y devuelve al
usuario el volumen de esta caja.
Como ejemplo, una caja con anchura = 20, altura = 10 y longitud = 50 tendrá un
volumen de 10000 (omitiendo unidades, ya que no son relevantes en este escenario). Por
ahora, supón que los valores proporcionados por el usuario son números válidos, pero si
tienes alguna idea sobre cómo hacerlo, puedes intentar hacer este script de tal manera que
sea resistente a los valores no válidos.
Ejemplo
Podemos imaginar nuestro programa como un árbol que comienza desde el tronco y se divide
gradualmente en ramas. El tronco es el comienzo del programa, y cada instrucción condicional
es un reflejo de una nueva rama. Las instrucciones condicionales se ejecutan sobre la base de
la decisión del usuario, los resultados de cálculos anteriores u otra información que el
programa tendrá en cuenta. JavaScript proporciona algunas formas de bifurcar la ejecución
del código, basado en condiciones arbitrarias. A partir de este capítulo, habrá más tareas y
código que deberás escribir, ya que ahora deberías tener a mano casi todas las herramientas
necesarias.
La instrucción if
if (condición) {
bloque de código
La palabra clave if debe ir seguida de la expresión entre paréntesis, que se evaluará como
booleana, y si el resultado es verdadero, se ejecuta el bloque de código que sigue a la
expresión condicional. Si la expresión se evalúa como falsa, el bloque de código NO se
ejecutará. El bloque de código debe separarse mediante corchetes.
console.log(isUserReady);
if (isUserReady) {
alert("¡Usuario listo!");
}
En el ejemplo, una alerta con el mensaje "¡Usuario listo!" se mostrará solo cuando el usuario
cierre el cuadro de diálogo de confirmación haciendo clic en el botón Aceptar. Pero si el
usuario cierra el cuadro de confirmación "¿Estás listo?" sin hacer clic en Aceptar, el mensaje
"¡Usuario listo!" el mensaje nunca se mostrará.
La instrucción if
En el ejemplo, hay una línea dentro del bloque de código if, lo que significa que podríamos
omitir las llaves alrededor del bloque. Sin embargo, aunque puede parecer tentador hacerlo,
siempre es mejor usar llaves. De esa forma, el código es más limpio y fácil de leer, y también
evita un error muy común que ocurre cuando se intenta agregar otra instrucción dentro de un
bloque if y se olvida agregar las llaves.
En el siguiente ejemplo, probablemente nos olvidamos de cerrar los dos comandos dentro del
bloque directamente detrás de la instrucción if. Comprueba cómo se comportará este
fragmento de código cuando lo ejecutes, según tu respuesta a la pregunta "¿Estás listo?":
if (isUserReady)
console.log("¡Usuario listo!");
alert("¡Usuario listo!");
Corrige este código cerrando ambos comandos (console.log y alert) en el bloque. Comprueba
cómo afectará esto al programa.
Como ya hemos mencionado, mediante el uso de bloques de datos, podemos hacer que las
constantes y variables declaradas dentro de ellos sean locales (no visibles desde el exterior).
Hablamos de esto en detalle en la sección de variables, por lo que aquí solo presentaremos
un ejemplo práctico para recordarlo:
La variable total se declara dentro del bloque. Podemos ponerle un valor, podemos leerlo,
pero todo esto solo dentro del bloque. Intentar referirse a él fuera del bloque terminará en un
error. El intérprete nos informará que no conoce tal variable.
La instrucción if - continuación
Este es un buen momento para recordarte las operaciones lógicas y de comparación, ya que
se usan más comúnmente como condiciones, especialmente en situaciones más complejas.
Veamos el ejemplo:
console.log(shippingCost);
Otra forma de escribir lo mismo es usar el operador lógico AND. Ahora podemos eliminar una
instrucción if y describir todo como una condición más compleja. Toma en cuenta que usamos
paréntesis adicionales para agrupar las operaciones lógicas seleccionadas. Esto nos permitirá
forzar su precedencia de ejecución.
if (isUserReady) {
console.log("¡Usuario listo!");
}
if (isUserReady == false) {
console.log("¡Usuario no esta listo!");
}
Esto funcionará como se esperaba, pero no se ve muy bien. ¿Y si en el futuro tenemos que
cambiar esta condición? ¿Recordaremos cambiarlo en ambos lugares? Este es un posible
error lógico futuro. Entonces, ¿qué podemos hacer? Podemos usar una palabra clave else.
La palabra clave else es una parte opcional de la instrucción if y nos permite agregar un
segundo bloque de código que se ejecutará solo cuando NO se cumpla la condición inicial.
Ambos bloques de código se separan mediante la palabra clave else.
if (condición) {
condición - código verdadero
} else {
condición - código falso
}
if (isUserReady) {
console.log("¡Usuario listo!");
} else {
console.log("¡Usuario no esta listo!");
}
Ahora solo tenemos una condición y estamos seguros de que se ejecutará uno de los dos
bloques de código. Esta estructura se usa con mucha frecuencia y es especialmente útil
cuando tenemos dos versiones alternativas del código.
if (condición_1) {
code
} else if (condición_2) {
code
} else if (condición_3) {
code
} else {
code
}
En el código visible en el ejemplo, solo se mostrará una alerta y JavaScript dejará de verificar las
condiciones después de que se haya cumplido la primera condición.
Para resumir lo que está pasando, podemos diseccionar cada caso por separado:
Si isOrderValid es verdadero
Y addInsurance es verdadero
Y hasPromoCode es falso, entonces se le suma INSURANCE_COST a shippingCost.
Tómate tu tiempo con este ejemplo y juega con los valores para entender qué está pasando y
cómo.
Operador condicional
Hemos hablado del operador condicional (ternario) en la parte del curso dedicada a los
operadores. Nos permite realizar una de dos acciones en función del valor del primer
operando. La mayoría de las veces se usa como una alternativa a la instrucción if ...
else cuando deseas asignar uno de dos valores a una variable, dependiendo de una
determinada condición. En tales casos, es simplemente más corto y un poco más legible que
la instrucción if... else. Veamos un ejemplo simple, sin usar un operador condicional:
El operador condicional devuelve los valores del segundo o tercer operando, pero si son
expresiones complejas, los calculará primero. Puedes usar este hecho para colocar los
comandos que se ejecutarán condicionalmente como estos operandos.
Sin embargo, tendría más sentido guardar el mismo fragmento de código de la siguiente
forma:
Es posible tener código más largo como operandos, pero es mejor usar sentencias if, ya que
es mucho más claro.
switch (expresión) {
case primera_coincidencia:
código
break;
case segunda_coincidencia:
código
break;
default:
código
}
Comienza con la palabra clave reservada switch seguida de la expresión a evaluar entre
paréntesis. El siguiente es el bloque de código que tiene una o más cláusulas de caso
(técnicamente es posible tener cero casos, pero esto no sería muy útil) seguido directamente
por un valor correspondiente a este caso y un carácter de dos puntos. Después de los dos
puntos, hay un bloque de código que se ejecutará cuando la expresión se evalúe con este
valor de caso. El bloque de código termina opcionalmente con la palabra clave break. Todos
los casos siguen la misma plantilla.
Además, puede estar presente un caso especial llamado default (por convención ubicado al
final de la instrucción switch; sin embargo, no es obligatorio). El caso default se ejecuta
cuando ninguno de los casos coincide con la expresión. La evaluación en sí se realiza con un
operador de comparación estricto (===) por lo que no solo debe coincidir el valor, sino
también el tipo de valor de caso y la expresión.
switch (gate) {
case "a":
alert("Puerta A: Vacía");
break;
case "b":
alert("Puerta B: Premio Mayor");
win = true;
break;
case "c":
alert("Puerta C: Vacía");
break;
default:
alert("No existe la Puerta " + String(gate));
}
if (win) {
alert("¡Ganador!");
}
Resumen
Las sentencias de control de flujo son una parte esencial de casi cualquier aplicación y las
usamos para verificar y manejar las entradas del usuario, los valores obtenidos de la web o los
resultados de casi cualquier operación. Nos permiten construir aplicaciones flexibles y
reactivas. La construcción más universal es la sentencia o instrucción if ... else.
Sin embargo, no olvides que en algunas situaciones será más conveniente usar un operador
condicional o una sentencia switch.
Tareas
Tarea 1
Ejemplo
alert("¡Bingo!");
} else {
alert("¡Fallaste!");
tarea 2
Ejemplo
let message = (number > 90 && number < 110) ? "¡Bingo!": "¡Fallaste!";
alert(message);
Tarea 3
Escribe una aplicación de calculadora simple. Solicite al usuario la siguiente entrada, uno por
uno: dos números y un carácter que representa una operación matemática de "+", "-", " *", o
"<código style="box-sizing: border-box;">/</código>". Si la entrada del usuario es válida,
calcula el resultado y muéstralo al usuario. Si la entrada del usuario no es válida, muestra un
mensaje que informa al usuario que se ha producido un error. Recuerda que el valor devuelto
por la función prompt es del tipo cadena. Puedes usar el método Number.isNaN para verificar
si se obtiene el número correcto después de la conversión. Por ejemplo, llamar
a Number.isNaN(10) devolverá false, mientras que Number.isNaN(NaN) devolverá true.
Ejemplo
let result;
switch (operand) {
} else {
alert(result);
El bucle while
Así que sabemos que los bucles nos permiten ejecutar un fragmento de código seleccionado varias
veces. Pero, ¿cuál sería el propósito de esto? Imagina que en un programa hemos creado un arreglo que
contiene información sobre los usuarios del sistema. Si quisiéramos mostrar el nombre de cada uno de
ellos en la consola, tendríamos que escribir console.log tantas veces como usuarios. Por ejemplo, para
100 usuarios escribimos 100 llamadas de console.log una debajo de otra. Se vería bastante raro, ¿no? En
su lugar, podemos usar un bucle que llame al mismo comando console.log 100 veces, pero para cada
elemento subsiguiente del arreglo. Cada iteración del bucle se referirá al usuario sucesivo. En un
momento, aprenderemos a usar diferentes bucles.
Comencemos con un ejemplo en el que no usamos un bucle. Nuestro objetivo es escribir los números
consecutivos 0, 10, 20, 30, 40, 50, 60, 70, 80 y 90 en la consola.
console.log(0); // -> 0
console.log(10); // -> 10
console.log(20); // -> 20
console.log(30); // -> 30
console.log(40); // -> 40
console.log(50); // -> 50
console.log(60); // -> 60
console.log(70); // -> 70
console.log(80); // -> 80
console.log(90); // -> 90
Con todo, hemos logrado nuestro efecto, pero se ve un poco extraño. Repetimos el mismo comando diez
veces, uno por uno. A primera vista, puede parecer que los comandos no son exactamente iguales. Sí,
cada vez que llamamos a console.log aparece un literal diferente como argumento: 0, 10, 20, etc.
Vamos a modificar el código para mostrar que es exactamente la misma acción.
let n = 0;
console.log(n); // -> 0
n += 10;
console.log(n); // -> 10
n += 10;
console.log(n); // -> 20
n += 10;
console.log(n); // -> 30
n += 10;
console.log(n); // -> 40
n += 10;
console.log(n); // -> 50
n += 10;
console.log(n); // -> 60
n += 10;
console.log(n); // -> 70
n += 10;
console.log(n); // -> 80
n += 10;
console.log(n); // -> 90
n += 10;
Usamos la variable n, que inicializamos con 0. Luego repetimos este fragmento de código idéntico diez
veces: mostramos el valor actual de la variable n en la consola y luego aumentamos este valor en 10.
console.log(n);
n += 10;
Esta es exactamente la situación en la que podemos usar un bucle. Usando el bucle while, reescribimos
nuestro código nuevamente. Dejamos la sentencia e inicialización de la variable n sin cambios. El
fragmento de código repetitivo se incluye en un bloque de código separado y, al usar la palabra while,
especificamos que debe ejecutarse siempre que el valor de la variable n sea inferior a 91.
let n = 0;
console.log(n); // -> 0, 10, 20, 30, 40, 50, 60, 70, 80, 90
n += 10;
while(condición) {
bloque de código
}
El ejemplo es correcto, pero aquí hay mucho código redundante, que no es realmente
necesario (solo lo hicimos en primer lugar para poder analizar el funcionamiento de las
instrucciones subsiguientes de una manera relativamente simple). Se logrará exactamente el
mismo efecto simplificando nuestro código como se muestra a continuación. Trata de analizar
exactamente de qué se tratan los cambios. Hemos utilizado para este propósito, entre otros,
los operadores que aprendimos recientemente.
while (!isOver) {
isOver = !confirm(`[${counter++}] ¿Continuar en el bucle?`);
}
do {
bloque de código
} while(condición);
Reescribamos nuestro último ejemplo usando do ... while en lugar de while. Nota que
esta vez la variable isOver no necesita inicializarse antes del bucle (la condición se verifica al
final del bucle y se llamará al cuadro de diálogo de confirmación antes de la primera prueba).
let isOver;
let counter = 1;
do {
isOver = !confirm(`[${counter++}] ¿Continuar en el bucle?`);
} while (!isOver);
El comportamiento del programa será idéntico al anterior, en el que usábamos while. Compara
ambos ejemplos, y deberías ver fácilmente las diferencias causadas por el uso de diferentes
sentencias de bucle.
En el siguiente ejemplo, demostramos lo que dijimos antes - un do ... while loop siempre itera
al menos una vez:
while (condition) {
console.log("Una iteración del bucle while."); // nunca se
ejecuta
}
do {
console.log("Una iteración del bucle do ... while."); //
ejecutado una vez
} while (condition);
El bucle for
El bucle for forma parte del segundo tipo de bucles, el que es mejor en situaciones en las que sabemos
cuántas veces se debe ejecutar el bucle (aunque esto no es necesario). La sintaxis del bucle for es un
poco más complicada y tiene el siguiente aspecto:
bloque de código
}
En los paréntesis después de la palabra for, esta vez no encontraremos una sola condición, como sucedió
en el bucle while. El interior de los paréntesis se divide en tres campos por punto y coma, y a cada
campo se le asigna un significado diferente. En cada uno de estos campos aparece una sentencia, que se
interpretará de la siguiente manera:
Las tres sentencias son opcionales, pero de hecho rara vez se nos escapa alguna. Echemos un vistazo
más de cerca a ellos.
La inicialización se ejecuta solo una vez, antes de la primera iteración del bucle. Por lo general, se usa
para inicializar (o declarar e inicializar) una variable que se usará como contador de bucle. Podemos usar
cualquier variable existente como contador, pero en general es una buena práctica declarar una nueva, ya
que esto hace que el código sea más limpio y más fácil de leer y entender. La declaración de la
inicialización es opcional y se puede dejar vacía, excepto si termina con un punto y coma.
Una condición es una expresión que se evalúa como un valor booleano antes de cada iteración del bucle.
Si esta expresión se evalúa como verdadera, el bucle ejecutará su código. En el caso de que la condición
se evalúe como falsa, el bucle finaliza y no se ejecutarán más iteraciones, y la ejecución del código salta
a la primera sentencia después del bucle for. La condición también es opcional, pero si se deja vacía, se
asumirá que siempre es verdadera, lo que generará un bucle infinito si no se utiliza ningún otro medio
para salir del bucle.
El incremento siempre se ejecuta al final de la iteración del bucle actual y, la mayoría de las veces, se
usa para incrementar (o disminuir, según la necesidad) un contador que se usa en una condición. Puede
ser cualquier expresión, no solo incremento/decremento. Esto también se puede dejar vacío.
Puede parecer un poco complicado, pero después del primer ejemplo simple, todo debería quedar más
claro. Usando el bucle for, intentaremos escribir enteros sucesivos del 0 al 9 en la consola, por lo que
haremos diez iteraciones:
console.log(i);
}
Como se muestra en la sintaxis del bucle for, hay tres expresiones dentro de los paréntesis. El let i = 0 es
una inicialización, i < 10 es una condición e i++ es un incremento. Ahora intentemos reescribir el mismo
ejemplo usando el bucle while:
let i = 0;
console.log(i);
i++;
Un ejemplo típico del uso de un bucle for, en el que sabemos el número de iteraciones por adelantado, es
cuando se revisan los elementos de un arreglo. Pasemos a otro ejemplo sencillo. Supongamos que
tenemos un arreglo de números enteros de cuatro elementos a nuestra disposición y nuestro sueño es
sumar todos estos números. No hay nada más fácil que recorrer el arreglo, tomando los elementos uno
por uno y agregándolos al resultado. Solo tenemos que recordar que antes de iniciar el bucle, el resultado
es 0.
Sencillo, ¿verdad? Pero hay una ligera incomodidad en el código. Hemos establecido el número de
iteraciones a cuatro. Es cierto que hay exactamente cuatro elementos en el arreglo, y en nuestro ejemplo
este número no cambia, pero no es una regla universal. En los programas normales, los arreglos suelen
cambiar dinámicamente durante la ejecución del programa (se agregan o eliminan elementos). En este
caso, definitivamente es mejor usar la propiedad de los arreglos llamada length (la mencionamos cuando
discutimos sobre los arreglos). Esta propiedad contiene el número actual de elementos del arreglo. Así
que nuestro ejemplo reescrito correctamente se verá así:
Bucles y arreglos
Intentemos jugar de nuevo con los arreglos. Esta vez el programa será un poco más complicado.
Queremos que el usuario ingrese nombres durante la ejecución del programa (usaremos el cuadro de
diálogo prompt), que se colocarán en el arreglo uno por uno. La introducción de nombres finalizará
cuando se pulse el botón Cancelar. Luego, escribiremos todo el contenido del arreglo (es decir, todos los
nombres ingresados) en la consola.
El arreglo names está inicialmente vacío. En cada iteración del bucle while llamamos al cuadro de
diálogo prompt. Si el usuario ingresa un nuevo nombre y presiona el botón OK, este nombre se
ingresará en la variable local name. Si el usuario hace clic en Cancelar, la variable contendrá un null.
Entonces verificamos en la instrucción condicional si el valor es diferente de null. Si es así, el valor de
la variable name se adjunta al final del arreglo names utilizando el método push (si no lo recuerdas,
vuelve al capítulo donde hablamos sobre arreglos). Si el valor es null, el valor de la
variable isOver se cambia para finalizar el bucle while.
Después de dejar el bucle while, pasamos por el arreglo (usando el bucle for y la propiedad length)
y mostramos todos los nombres dentro del arreglo. De esta manera, hemos hecho algo absolutamente
nuevo. Utilizando arreglos, bucles e interacción con el usuario (cuadro de diálogo prompt) hemos
creado y llenado una estructura de datos dinámica. Toma en cuenta que mientras escribes este programa,
no está claro cuántos elementos habrá en el arreglo, o incluso qué elementos habrá.
Revisando los elementos del arreglo, usamos una variable index que inicializamos con el valor de 0 (el
índice del primer elemento del arreglo) y la incrementamos de uno en uno durante cada iteración.
Obviamente, este no es el único enfoque. Por ejemplo, si quisiéramos recorrer los elementos del arreglo
en orden inverso, inicializaríamos la variable de índice con el valor del índice más grande y lo
disminuiríamos de uno en uno durante cada iteración. Tampoco hay nada que nos impida saltar algunos
elementos a la vez, incrementando o decrementando la variable índice en un valor mayor que uno. Echa
un vistazo al siguiente ejemplo:
for ... of
Además del bucle for regular, hay dos versiones específicas, una de las cuales, for... of, está
dedicada para usar con arreglos (y otras estructuras iterativas, que sin embargo están más
allá del alcance de este curso). En un bucle de este tipo, no especificamos explícitamente
ninguna condición ni número de iteraciones, ya que se realiza exactamente tantas veces como
elementos haya en el arreglo indicado.
Volvamos a nuestro ejemplo de sumar los números del arreglo de cuatro elementos. En este
ejemplo, usamos un bucle for simple:
La versión de este programa que usa el bucle for ... of se verá ligeramente diferente:
Entre corchetes después de la palabra for, no encontrarás tres campos separados por punto y
coma. Hay una declaración de variable, seguida de la palabra of y luego un arreglo, cuyos
elementos recorreremos (variable o literal). En nuestro ejemplo, for (let number of values)
significa que la variable number contendrá los elementos subsiguientes del arreglo values en
cada iteración. La sintaxis de este bucle es la siguiente:
En la práctica, con mucha más frecuencia que las diferentes versiones del bucle for, el método
forEach se usa para iterar a través de los elementos de un arreglo. Este es uno de los
métodos array type. Su uso requiere la capacidad para escribir tus propias funciones, por lo
que volveremos a él en la sección de funciones del curso.
Veamos un ejemplo más. Esta vez, declaramos un arreglo de ciudades, que contiene objetos
que describen algunas de las ciudades más grandes del mundo. Cada objeto contiene los
campos de nombre y población. Usando el bucle for... of, recorremos este arreglo y
mostramos información sobre todas las ciudades que tienen más de 20 millones de
habitantes.
let cities = [
{ name: "New York", population: 18.65e6 },
{ name: "Cairo", population: 18.82e6 },
{ name: "Mumbai", population: 19.32e6 },
{ name: "São Paulo", population: 20.88e6 },
{ name: "Mexico City", population: 21.34e6 },
{ name: "Shanghai", population: 23.48e6 },
{ name: "Delhi", population: 25.87e6 },
{ name: "Tokyo", population: 37.26e6 }
];
Ejecuta el código y luego experimente con las condiciones (por ejemplo, muestra todas las
ciudades con más de 20 millones de habitantes pero menos de 25 millones, etc.).
for ... in
También existe una versión del bucle for que nos permite recorrer campos de objetos. La
sintaxis es for ... in. Itera a través de todos los campos del objeto indicado, colocando los
nombres de estos campos (o claves) en la variable. En el ejemplo, usamos un objeto que
contiene datos sobre un usuario:
let user = {
name: "Calvin",
surname: "Hart",
age: 66,
email: "[email protected]"
};
En cada iteración del bucle, los nombres de los campos sucesivos del objeto de usuario se
asignan a la variable key, es decir: nombre, apellido, edad, correo electrónico. Luego los
escribimos en la consola. Pero, ¿y si queremos recuperar los valores almacenados en los
campos con estos nombres? Para obtener acceso al campo especificado, usamos la notación
de puntos (la parte del curso dedicada a los tipos de datos), es decir, después del nombre del
objeto, escribimos un punto y el nombre del campo (key). La clave dada en esta notación
siempre se trata como un literal. En el bucle for... in, este enfoque no funcionará porque el
nombre del campo (key) se coloca en una variable. Afortunadamente, tenemos una solución
alternativa, la notación entre paréntesis. Te permite referirte al campo del objeto seleccionado
usando corchetes (como en los arreglos). En el corchete detrás del nombre del objeto,
colocamos el nombre del campo, que puede ser un literal o una variable que contenga ese
nombre.
Usando la notación de corchetes, mejoramos nuestro ejemplo al mostrar no solo las claves,
sino también sus valores asignados.
En el ejemplo, podemos ver un bucle infinito while del que se saldrá usando break después
de que la variable i sea mayor o igual a 5. Tal uso de break es solo de importancia ilustrativa,
porque tendría más sentido incluir esta condición directamente en la construcción del while.
let i = 0;
// Un bucle infinito
while (true){
console.log(i);
i++;
if (i >= 5) {
break;
}
}
Al igual que break, continue se puede usar en bucles (pero no en un switch). Cuando se usa,
se aplica al bucle circundante más cercano. La instrucción continue, a diferencia de break, no
finaliza todo el bucle, sino que inicia la siguiente iteración de este bucle. Podemos pensar en
ello como saltar directamente al final de la iteración actual.
El programa escribe números del 0 al 9 en la consola, pero salta el número 3. Esto sucede
porque cuando i es igual a 3, se ejecuta la instrucción continue, finalizando esta iteración (y
omitiendo el console.log) y se comienza la siguiente iteración.
Tanto break como continue no se usan con mucha frecuencia. Definitivamente no deberíamos
usarlos cuando podemos terminar el bucle con una condición construida correctamente. Son
útiles en situaciones de emergencia, cuando sucede algo inusual en bucles con iteraciones
más complejas.
Para entender esto mejor, observa este ejemplo ligeramente modificado de un switch:
switch (gate) {
case "a":
alert("Puerta A: Vacía");
case "b":
alert("Puerta B: Premio Mayor");
win = true;
case "c":
alert("Puerta C: Vacía");
default:
alert("No existe la puerta " + String(gate));
}
if (win) {
alert("¡Ganador!");
}
La única diferencia es que ahora no hay palabras clave break. Ejecuta este código y verifica
qué sucede cuando se da la respuesta "a" al cuadro de diálogo. Ahora se muestran todas las
alertas, incluso la predeterminada.
switch (gate) {
case "a":
case "A":
case 1:
case "1":
alert("Puerta A: Vacía");
break;
case "b":
case "B":
case 2:
case "2":
alert("Puerta B: Premio Mayor");
win = true;
break;
case "c":
case "C":
case 3:
case "3":
alert("Puerta C: Vacía");
break;
default:
alert("No existe la puerta " + String(gate));
}
if (win) {
alert("¡Ganador!");
}
El código visible en el ejemplo se comportará igual cuando se proporcione "a", "A", 1 o "1"
como respuesta al cuadro de dialogo.
La palabra clave break - continuación
La última parte importante es que si se necesita un código más complejo dentro de un caso
determinado, debemos colocar ese código en bloques de código separados rodeándolo
adicionalmente con llaves. Esto aumentará la legibilidad del código y permitirá la declaración
de variables solo en el alcance del caso dado.
switch (gate) {
case "a": {
let message = "Puerta A";
console.log(message);
break;
}
case "b": {
let message = "Puerta B";
console.log(message);
break;
}
case "c": {
let message = "Puerta C";
console.log(message);
break;
}
default:
alert("No existe la puerta " + String(gate));
}
if (win) {
alert("¡Ganador!");
}
Resumen
Sin el uso de un bucle, es difícil imaginar la escritura de programas. Su uso no solo facilita el
trabajo, sino que a menudo permite crear soluciones que no estarían disponibles sin ellos.
JavaScript proporciona muchos mecanismos que permiten repetir ciertas acciones en bucles,
recorrer elementos de arreglos, iterar a través de campos de objetos, etc. Hemos discutido
solo los más básicos, pero el while, o diferentes versiones del for, deberían ser suficiente para
crear programas bastante avanzados.
Tareas
Tarea 1
Escribe un fragmento de código que escriba números del 100 al 0 en la consola, pero en
pasos de 10 en 10; entonces sería 100, 90, 80... etc.
Ejemplo
console.log(i);
Tarea 2
Modifica el programa anterior para que le pida al usuario el primer y último número a usar en
lugar de 100 y 0 (pista: use el cuadro de diálogo prompt). Comprueba si los valores
introducidos son correctos (que el valor inicial sea mayor que el valor final).
Ejemplo
console.log(i);
Tarea 3
Escribe un programa que primero escriba todos estos números en la consola, luego solo los
que son pares (pista: el residuo de dividir un número par entre 2 es igual a 0), luego solo los
que son mayores que 10 y al mismo tiempo menor que 60.
Ejemplo
let numbers = [21, 45, 100, 12, 11, 78, 61, 4, 39, 22];
console.log(number);
if (number % 2 === 0) {
console.log(number);
console.log(number);
Tarea 4
Escribe un programa utilizando un bucle que le pida al usuario el nombre de una película
(primer cuadro de dialogo) y su calificación de www.imdb.com (segundo cuadro de dialogo). El
programa te permitirá ingresar tantas películas como desees en el arreglo de películas. Cada
elemento del arreglo será un objeto, que constará de dos campos: título e imdb. La entrada se
completa si el usuario presiona Cancelar en el cuadro de diálogo. Luego, el programa debe
imprimir primero en la consola todas las películas que tienen una calificación inferior a 7, luego
aquellas cuya calificación sea mayor o igual a 7. Escribe el nombre de la película y su
calificación uno al lado del otro, por ejemplo:< /p>
Perdido en la Selva (7.7)
Ejemplo
while (true) {
break
} else {
movies.push({
title: title,
rating: Number(rating)
});
if (movie.rating < 7) {
console.log(`${movie.title} (${movie.rating})`);
}
console.log("Películas con calificaciones superiores a 7:");
if (movie.rating >= 7) {
console.log(`${movie.title} (${movie.rating})`);
break;
Tarea 5
El contenido del objeto que describe la posición del barco llamado "Mareno" se muestra en la
consola.
El código que se presenta a continuación se usa para esto. Completa el programa declarando
el objeto que falta en lugar de los tres puntos.
let vessel = {
LATITUD: 40.07288,
LONGITUD: 154.48535,
CURSO: 285.6,
VELOCIDAD: 14.0,
OMI: 9175717,
NOMBRE: "MARENO"
Tarea 6
Modifica el programa de calculadora que creaste en el Módulo 4, Sección 2, de tal manera que
funcione en el bucle hasta que el usuario ingrese S en cualquiera de los cuadros de dialogo.
Ejemplo
while (true) {
let result;
break;
firstNumber = Number(firstNumber);
secondNumber = Number(secondNumber);
if (!Number.isNaN(firstNumber) && !Number.isNaN(secondNumber)) {
switch (operand) {
case "+":
break;
case "-":
break;
case "*":
break;
case "/":
break;
default:
} else {
alert(result);
Objetivos
Familiarizar al estudiante con:
Bucles (qué son los bucles, el bucle while, el bucle do-while, el bucle for, el bucle for-
of, el bucle for-in, las instrucciones break y continue)
Escenario
Podemos mejorar un poco nuestro programa de lista de contactos mediante el uso de bucles.
Ahora puedes intentar mostrar no solo el primer o último contacto, sino todos los contactos de
la lista, independientemente de su número.
Además, intenta encerrar todo el programa en un bucle para que al usuario se le pregunte
repetidamente qué quiere hacer. El usuario puede optar por:
let contacts = [{
email: "[email protected]"
}, {
email: "[email protected]"
}, {
email: "[email protected]"
}];
Funciones
Hablamos de funciones en el capítulo sobre variables, cuando discutimos el alcance de la visibilidad de
las variables locales declaradas con la palabra clave var. Aprendimos en esa ocasión qué son las
funciones, pero en este capítulo las veremos mucho más de cerca, ampliando nuestro conocimiento sobre
el tema.
Entonces, ¿qué es una función? Es una pieza de código separada, que constituye un cierto todo lógico
cerrado, destinado a realizar una tarea específica. Por lo general, asignamos un nombre a esta pieza de
código separada. Con este nombre podemos llamarlo (ejecutarlo) muchas veces en diferentes lugares del
programa.
Esto es una simplificación, porque hay funciones que no tienen nombre, por ejemplo, funciones
anónimas (hablaremos de ellas más adelante). Por el momento, supongamos que una función tiene un
nombre, que le damos al declararla. Este nombre se utiliza cuando se llama o invoca la función, es decir,
cuando se ejecuta el código contenido en ella.
La declaración e invocación de funciones son independientes entre sí, lo que veremos en un momento.
Existen muchas razones, una de las más importantes es dividir el código en algunas partes lógicamente
independientes. Tal modularidad aumenta la legibilidad del código: es más fácil escribir y analizar un
programa que no es una secuencia de instrucciones individuales. También permite probar fácilmente
fragmentos de código cerrados en funciones independientemente del resto del programa.
Una razón muy importante para usar una función es la reutilización del código: si repites la misma
secuencia de instrucciones en el programa en muchos lugares, puedes colocar esta secuencia en una
función, y en esos lugares solo tiene que invocar a la función.
El programa calculará y mostrará la temperatura media diaria sobre la base de los datos
proporcionada (24 mediciones de temperatura, en intervalos de una hora, a partir de la
medianoche). En el programa declaramos la variable temperatures, que será una tabla de 24
elementos con las medidas obtenidas.
Tenemos medidas para dos días consecutivos, para lo cual haremos cálculos. La temperatura
media, por supuesto, se calcula sumando todos los valores y dividiendo el resultado entre la
cantidad.
A primera vista, puedes ver que el fragmento de código responsable de un cálculo se repite
dos veces. En dos lugares del programa, usamos la misma secuencia de instrucciones, por lo
que valdría la pena pensar en crear una función a partir de ellas.
Lo haremos en varias etapas, introduciendo algunos conceptos nuevos relacionados con las
funciones. Comencemos con una declaración de función.
let temperatures;
let sum;
let meanTemp;
temperatures = [12, 12, 11, 11, 10, 9, 9, 10, 12, 13, 15, 18, 21, 24,
24, 23, 25, 25, 23, 21, 20, 19, 17, 16];
sum = 0;
sum += temperatures[i];
temperatures = [17, 16, 14, 12, 10, 10, 10, 11, 13, 14, 15, 17, 22,
27, 29, 29, 27, 26, 24, 21, 19, 18, 17, 16];
sum = 0;
sum += temperatures[i];
Declaración de funciones
Al igual que con las variables, las funciones deben declararse antes de que podamos usarlas.
La sintaxis para la declaración de funciones se ve así:
function functionName() {
código
}
Este tipo de declaración de función en JavaScript se denomina declaración de función. Una
instrucción de función comienza con la palabra clave function seguida del nombre de la
función. Los nombres de las funciones deben seguir las mismas reglas que los nombres de las
variables y también deben ser significativos. Después del nombre de la función, hay
paréntesis que opcionalmente pueden tener parámetros de función, que discutiremos en un
momento. Después de los paréntesis viene el cuerpo de la función, que se compone de
cualquier cantidad de instrucciones (un bloque de código).
Intentemos declarar una función de acuerdo con estas reglas, que contendrá un fragmento de
nuestro código de programa que calcula la temperatura media diaria. Lo
llamaremos getMeanTemp. Por ahora, la función usará variables, declaradas fuera de ella (en
el contexto circundante). De hecho, prácticamente nunca se hace así, pero lo trataremos en
una de las etapas posteriores.
let temperatures;
let sum;
let meanTemp;
function getMeanTemp() {
sum = 0;
for (let i = 0; i < temperatures.length; i++) {
sum += temperatures[i];
}
meanTemp = sum / temperatures.length;
}
Invocación de Funciones
Para invocar a una función, necesitamos escribir un nombre de función seguido de paréntesis. Por lo
tanto, nuestro ejemplo completo debería verse así:
let temperatures;
let sum;
let meanTemp;
function getMeanTemp() {
sum = 0;
for (let i = 0; i < temperatures.length; i++) {
sum += temperatures[i];
}
meanTemp = sum / temperatures.length;
}
temperatures = [12, 12, 11, 11, 10, 9, 9, 10, 12, 13, 15, 18, 21,
24, 24, 23, 25, 25, 23, 21, 20, 19, 17, 16];
getMeanTemp();
console.log(`mean: ${meanTemp}`); // -> mean: 16.666666666666668
temperatures = [17, 16, 14, 12, 10, 10, 10, 11, 13, 14, 15, 17,
22, 27, 29, 29, 27, 26, 24, 21, 19, 18, 17, 16];
getMeanTemp();
console.log(`mean: ${meanTemp}`); // -> mean: 18.083333333333332
Por lo general, las funciones se declaran antes de llamarlas, principalmente al comienzo del código. Sin
embargo, esta es solo una buena práctica, que aumenta la legibilidad del código, no un requisito de
sintaxis de JavaScript. Las declaraciones de funciones se mueven automáticamente a la parte superior
del alcance, por lo que podemos usarlas antes de la declaración, siempre que estén en el alcance.
Entonces el código:
function showName() {
console.log(name);
}
function showName() {
console.log(name);
}
Ya sabemos qué es una declaración y una invocación de función. Es hora de echar un vistazo más de
cerca a su contenido. Comencemos con las variables que usamos.
En nuestro código, un ejemplo de tal variable es sum. Aunque la hemos declarado fuera de la
función getMeanTemp (es una variable global), solo la referimos dentro de la función. Por lo
tanto, una declaración global es innecesaria. Pongámoslo en orden, declarando sum
localmente.
let temperatures;
let meanTemp;
function getMeanTemp() {
let sum = 0;
sum += temperatures[i];
temperatures = [12, 12, 11, 11, 10, 9, 9, 10, 12, 13, 15, 18, 21, 24,
24, 23, 25, 25, 23, 21, 20, 19, 17, 16];
getMeanTemp();
getMeanTemp();
La sentencia return
Las funciones que han sido invocadas ejecutan una secuencia de instrucciones contenidas en
su cuerpo. El resultado de esta ejecución puede ser un valor determinado que tal vez
queramos almacenar en alguna variable. La palabra clave return viene a ayudarnos en este
caso. ¿Para qué sirve exactamente return?
Primero, hace que la función termine exactamente donde aparece esta palabra,
incluso si hay más instrucciones.
Segundo, nos permite devolver un valor dado desde dentro de la función al lugar
donde fue invocada.
function showMsg() {
console.log("mensaje 1");
return;
console.log("mensaje 2");
}
Como era de esperarse, la invocación termina mostrando solo el primer mensaje "mensaje 1",
luego el return interrumpe la función. En la práctica, usar return aquí no tendría mucho
sentido. Hace que nunca se ejecute console.log("mensaje 2"). Por lo tanto, sería más
fácil no escribir una segunda llamada console.log en lo absoluto.
Sin embargo, usando las instrucciones condicionales, podemos, por ejemplo, reaccionar a
errores dentro de la función y, en ciertas situaciones, interrumpir la función inmediatamente.
En el ejemplo, declaramos una función simple getTrue, que siempre devuelve verdadero. Presta
atención a la invocación de la función: almacenamos el resultado en la variable test. Como puedes
adivinar, esta variable tendrá el valor booleano true o verdadero.
function getTrue() {
return true;
}
Volvamos al ejemplo con las temperaturas medias. Hasta ahora, los cálculos realizados dentro de la
función getMeanTemp se han realizado sobre la variable global meanTemp. Cambiaremos esto. Dentro
de la función, declararemos la variable local result, que contendrá el resultado calculado, y
usaremos return para devolverlo. La variable global meanTemp contendrá el resultado de la
invocación o llamada a la función, es decir, la primera vez, 16.666666666666668 y la segunda
vez, 18.0833333333333332.
let temperatures;
let meanTemp;
function getMeanTemp() {
let sum = 0;
let result;
for (let i = 0; i < temperatures.length; i++) {
sum += temperatures[i];
}
result = sum / temperatures.length;
return result;
}
temperatures = [12, 12, 11, 11, 10, 9, 9, 10, 12, 13, 15, 18, 21,
24, 24, 23, 25, 25, 23, 21, 20, 19, 17, 16];
meanTemp = getMeanTemp();
console.log(`mean: ${meanTemp}`);
temperatures = [17, 16, 14, 12, 10, 10, 10, 11, 13, 14, 15, 17,
22, 27, 29, 29, 27, 26, 24, 21, 19, 18, 17, 16];
meanTemp = getMeanTemp();
console.log(`mean: ${meanTemp}`);
La variable result en realidad solo se usa para almacenar temporalmente el valor que se devolverá.
Entonces podemos simplificar el código de la función aún más. Omitiremos la
variable result colocando la operación apropiada directamente después de return.
function getMeanTemp() {
let sum = 0;
for (let i = 0; i < temperatures.length; i++) {
sum += temperatures[i];
}
return sum / temperatures.length;
}
La variable meanTemp también se ha vuelto un poco redundante. Almacenamos el resultado de la
llamada de función en él, que luego se muestra en la consola. Esto también se puede simplificar
colocando la llamada de función getMeanTemp directamente en console.log (sin el uso de la
variable meanTemp).
let temperatures;
function getMeanTemp() {
let sum = 0;
for (let i = 0; i < temperatures.length; i++) {
sum += temperatures[i];
}
return sum / temperatures.length;
}
temperatures = [12, 12, 11, 11, 10, 9, 9, 10, 12, 13, 15, 18, 21,
24, 24, 23, 25, 25, 23, 21, 20, 19, 17, 16];
console.log(`mean: ${getMeanTemp()}`);
temperatures = [17, 16, 14, 12, 10, 10, 10, 11, 13, 14, 15, 17,
22, 27, 29, 29, 27, 26, 24, 21, 19, 18, 17, 16];
console.log(`mean: ${getMeanTemp()}`);
Nuestra getMeanTemp poco a poco empieza a parecerse a una función normal. Es una pieza de código
lógicamente independiente, devuelve un valor calculado y opera en variables locales. Queda un pequeño
problema por resolver. Contamos la media usando los datos contenidos en la variable global
temperatures. Y la función debe ser independiente de lo que sucede fuera de ella. Al mismo tiempo,
debería permitirnos calcular el valor medio para diferentes datos. ¿Cómo reconciliamos estas dos
demandas en conflicto? Los parámetros de la función se utilizan para esto.
Parámetros
En primer lugar, el uso de parámetros en funciones es opcional. Puede haber funciones que no tengan
parámetros, como hemos visto en nuestros ejemplos anteriores, al igual que puede haber funciones que
no devuelvan valores. Sin embargo, la mayoría de las veces creamos funciones que tienen parámetros
definidos y valores devueltos.
En JavaScript, los parámetros de una función aparecen en su declaración. Estos son nombres separados
por comas, colocados entre paréntesis después del nombre de la función. Cada parámetro dentro de una
función será tratado como una variable local. Una función cuya definición especifica los parámetros
debe invocarse de forma adecuada. Cuando se llama a una función de este tipo, los valores (literales,
variables, llamadas de función) deben colocarse entre paréntesis después de su nombre y se tratan como
parámetros dentro de la función. Los valores dados durante una llamada se llaman argumentos. Los
argumentos, si hay más de uno, se separan por comas y deben pasarse en el mismo orden que los
parámetros que definimos en la declaración de la función.
Veamos una función simple que suma dos valores. Lo llamaremos add.
function add(first, second) {
return first + second;
}
En la declaración de la función, entre paréntesis, ponemos dos parámetros: first y second. Los
nombres de los parámetros, al igual que las variables, deben estar relacionados con su propósito; en este
caso, lo hemos hecho de manera diferente para enfatizar que el orden de los parámetros es importante.
Dentro de la función de suma, estos parámetros se tratan como variables locales, cuyos valores se darán
cuando se invoque a la función.
Puedes pasar cualquier tipo de datos como argumentos a la función, tanto simples como complejos.
Escribamos la función getElement, que devolverá el elemento seleccionado del arreglo, siendo el
arreglo y el índice del elemento los parámetros de la función.
function getMeanTemp(temperatures) {
let sum = 0;
for (let i = 0; i < temperatures.length; i++) {
sum += temperatures[i];
}
return sum / temperatures.length;
}
let day1 = [12, 12, 11, 11, 10, 9, 9, 10, 12, 13, 15, 18, 21, 24,
24, 23, 25, 25, 23, 21, 20, 19, 17, 16];
console.log(`mean: ${getMeanTemp(day1)}`); // -> mean:
16.666666666666668
let day2 = [17, 16, 14, 12, 10, 10, 10, 11, 13, 14, 15, 17, 22,
27, 29, 29, 27, 26, 24, 21, 19, 18, 17, 16];
console.log(`mean: ${getMeanTemp(day2)}`); // -> mean:
18.083333333333332
Probablemente ya puedas señalar una cosa más: al llamar al método console.log (una función
relacionada con el objeto de la consola) también le pasamos un argumento: una cadena que se mostrará
en la consola. Esto significa que hemos estado usando los parámetros de la función desde el comienzo
del curso.
Sombreado
Como mencionamos anteriormente, los parámetros se tratan dentro de la función como variables locales.
Y al igual que las variables locales explícitamente declaradas dentro de una función, sombrean las
variables globales del mismo nombre (o más generalmente, las variables del ámbito externo). Volvamos
por un momento al ejemplo de la suma de números. La función add tiene dos
parámetros: first y second. Al mismo tiempo, declararemos, fuera de la función, variables globales
denominadas first, second, third y fourth.
first, será tratada como el parámetro con tal nombre que sombrea a la variable
global first (es decir, operaremos sobre el valor pasado como primer argumento)
second, también será tratada como el parámetro de la función (el valor pasado como segundo
argumento)
third, será tratada como una variable global, porque dentro de la función no hay una variable
local ni un parámetro con ese nombre.
fourth, será tratada como global, al igual que third.
Por supuesto, fuera de la función, cada uno de estos nombres se referirá a variables globales.
Analiza el ejemplo cuidadosamente, anotando los valores específicos que se pasan a la función de suma
en cada una de las cuatro llamadas.
También intenta ejecutar y analizar un ejemplo más simple, en el que puede sombrear variables con
parámetros y variables locales.
function test(a) {
let b = 10;
console.log(a); // parameter a
console.log(b); // local variable b
console.log(c); // global variable c
}
test(1); // -> 1
// -> 10
// -> 300
Validación de parámetros
¿Recuerdas que dijimos que a veces usamos la palabra clave return para interrumpir
funciones en caso de errores? Un buen ejemplo es la validación de parámetros de funciones.
Volvamos al ejemplo con la función getMeanTemp. La última versión que escribimos necesita
un arreglo de números como argumento. Antes de comenzar el cálculo, podemos verificar si el
valor que se le pasó es realmente un arreglo.
function getMeanTemp(temperatures) {
if (!(temperatures instanceof Array)) {
return NaN;
}
let sum = 0;
for (let i = 0; i < temperatures.length; i++) {
sum += temperatures[i];
}
return sum / temperatures.length;
}
6!=6 x 5 x 4 x 3 x 2 x 1 = 720
Intentemos escribir una función que calcule el factorial de un número dado. Tomará el parámetro n y
devolverá el valor calculado.
En este caso, usamos el método iterativo para calcular el factorial, es decir, usamos un bucle en el que,
durante cada iteración, multiplicamos el resultado anterior por otro elemento de la secuencia. Después de
la primera iteración, el resultado es 6, después de la segunda, 30, después de la tercera, 120, y así
sucesivamente. Las iteraciones se repiten hasta el último elemento significativo de la secuencia, es decir,
hasta el valor 2 (de ahí la condición de terminar el bucle n > 1).
Sin embargo, la definición de un factorial se puede escribir de una manera ligeramente diferente. Será el
factorial del elemento anterior n - 1 multiplicado por n.
Por ejemplo, 6! es 5! multiplicado por 6. Tal definición factorial usa la recursividad, es decir, se refiere a
una función a sí misma (pero con un argumento diferente). Una recursividad es un mecanismo que
permite simplificar la notación formal de muchas funciones matemáticas y presentarlas de forma
elegante. También podemos usar con éxito la recursividad en la programación.
Para obtener un código más corto y compacto, en lugar de una instrucción condicional if, usamos el
operador condicional ternario. En él, verificamos si el argumento n es mayor que 1. Dependiendo de eso,
devolvemos el resultado de multiplicar el número n por el resultado de la llamada factorial (n - 1), o el
valor 1.
La recursividad se usa comúnmente en la programación. Sin embargo, como con cualquier solución, la
recursividad debe manejarse con cuidado. No deberíamos usarlo donde no podamos estimar la cantidad
de llamadas incrustadas. También debemos tener mucho cuidado al formular la condición que
interrumpirá las llamadas a la secuencia de funciones: los errores pueden hacer que el programa se
suspenda.
function showMessage(message) {
console.log(`Mensaje: ${message}`);
}
let sm = showMessage;
Podemos almacenar cualquier función que sea accesible en este ámbito en una variable y usar un
operador de llamada de función () para ejecutarla. Podemos verificar que la variable sm ahora es una
función usando el operador typeof.
Pero es importante recordar que cuando asignamos una función a una variable, no usamos un operador
de llamada de función, ya que ejecutaría la función y asignaría el resultado de la función a una variable,
y no a la función misma.
function doNothing() {
return undefined;
}
En el ejemplo, el resultado de la llamada a la función doNothing (es decir, el valor indefinido devuelto
por la función) se almacena en la variable a, mientras que la función doNothing en sí se almacena en la
variable b (o más precisamente, una referencia a la función se almacena en la variable b).
Esta propiedad es especialmente útil cuando se pasa la función como parámetro de llamada a otras
funciones, sobre las que pronto aprenderemos más. Por ahora, probemos que algo como esto es
realmente factible.
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
La función operation toma como primer argumento la función (parámetro func) y la llama con los otros
dos argumentos pasados (parámetros first y second).
Expresiones de funciones
Para almacenar una función en una variable o pasarla como argumento para llamar a una
función, no necesariamente tienes que declararla previamente y usar su nombre. Volvamos a
nuestro ejemplo con la función add:
function add(a, b) {
return a + b;
}
console.log(operation(function(a, b) {
return a * b;
}, 10, 20)); // -> 200
En el primer paso, declaramos la operación de la función (es una función con nombre, y aquí
usamos la declaración de función, así que nos referiremos a la función por su nombre). En el
siguiente paso, definimos una función anónima, que almacenamos en la
variable myAdd (usamos una expresión de función). Llamamos a la función de operación,
pasando la función myAdd y los valores 10 y 20 como argumentos.
console.log(operation(function(a, b) {
return a * b;
}, 10, 20)); // -> 200
En el primer paso, declaramos la operación de la función (es una función con nombre, y aquí
usamos la declaración de función, así que nos referiremos a la función por su nombre). En el
siguiente paso, definimos una función anónima, que almacenamos en la
variable myAdd (usamos una expresión de función). Llamamos a la función de operación,
pasando la función myAdd y los valores 10 y 20 como argumentos.
Callbacks
Las funciones que se pasan como argumentos a otras funciones pueden parecer bastante
exóticas y no muy útiles, pero de hecho, son una parte muy importante de la programación.
Tan importante que incluso tienen su propio nombre. Son funciones callback. Como hemos
visto en ejemplos anteriores, una función que recibe una callback como argumento puede
llamarla en cualquier momento. Es importante destacar que, en nuestros ejemplos, la callback
se ejecuta de forma síncrona, es decir, se ejecuta en un orden estrictamente definido que
resulta de dónde se coloca entre las otras instrucciones.
Callbacks síncronas
La ejecución síncrona es la forma más natural de ver cómo funciona el programa. Las
instrucciones posteriores se ejecutan en el orden en que se colocan en el código. Si llama a
una función, las instrucciones que contiene se ejecutarán en el momento de la llamada. Si le
pasamos otra función a esta función como argumento, y también la llamamos dentro de una
función externa, entonces todas las instrucciones mantendrán su orden natural.
console.log('inner 1');
console.log('outer 1');
callback();
console.log('outer 2');
console.log('test 1');
outer(inner);
console.log('test 2');
La ejecución del código anterior hará que la consola imprima el siguiente texto en este orden
exacto:
test 1
outer 1
inner 1
outer 2
test 2
salida
Por lo tanto, se mantiene el orden de acciones resultante del orden de llamada de los
comandos y funciones. Sin embargo, este orden puede verse alterado en determinadas
circunstancias especiales.
Callbacks asíncronas
El funcionamiento asíncrono de programas es un tema bastante complejo, que depende en
gran medida de un lenguaje de programación en particular y, a menudo, también del entorno.
En el caso de que JavaScript del lado del cliente se ejecute en un navegador, se limita a la
programación basada en eventos, es decir, la respuesta asincrónica a ciertos eventos. Un
evento puede ser una señal enviada por un temporizador, una acción del usuario (por ejemplo,
presionar una tecla o hacer clic en un elemento de interfaz seleccionado) o información sobre
la recepción de datos del servidor.
Usando las funciones apropiadas, combinamos un tipo específico de evento con una función
callback seleccionada, que se llamará cuando ocurra el evento.
Uno de los casos más simples cuando existe una ejecución asíncrona de instrucciones es el
uso de la función setTimeout. Esta función toma otra función (una callback) y el tiempo
expresado en milisegundos como argumentos. La función callback se ejecuta después del
tiempo especificado y, mientras tanto, se ejecutará la siguiente instrucción del programa
(ubicada en el código después del setTimeout).
Por lo tanto, el momento en que se llama a la función callback no está determinado por su
orden, sino por un retraso impuesto arbitrariamente. El retraso solo se aplica a la función
callback dado a setTimeout, mientras que el resto del código aún se ejecuta de forma
síncrona.
test 1
outer 1
outer 2
test 2
...
inner 1
let inner = function() {
console.log('inner 1');
console.log('outer 1');
console.log('outer 2');
console.log('test 1');
outer(inner);
console.log('test 2');
El resultado de la ejecución del programa debe ser los siguientes mensajes, que aparecerán
en la consola:
outer 1
outer 2
test 2
...
inner 1
inner 1
inner 1
inner 1
inner 1
console.log('inner 1');
console.log('outer 1');
console.log('outer 2');
setTimeout(function(){
clearInterval(timerId);
}, 5500);
console.log('test 1');
outer(inner);
window.addEventListener("click", function() {
console.log("¡hizo clic!");
});
Intenta ejecutar el código de muestra. Nada especial debe suceder inmediatamente después
de que se inicie. Solo cuando haga clic en cualquier parte de la página, aparecerá un mensaje
en la consola: "¡hizo clic!". No se llama a nuestra función hasta que se produce el evento
"click", que es absolutamente asíncrono. Mientras tanto, entre clics posteriores, se podría
ejecutar el resto del programa, si tuviéramos el antojo de escribirlo.
De hecho, no es muy buena idea conectar una respuesta de clic a un objeto de ventana. La
mayoría de las veces, tales acciones están asociadas a elementos específicos de la interfaz
(botones, casillas de verificación, etc.) que permiten su diferenciación. Sin embargo, como ya
dijimos, volveremos a este tema en la siguiente parte del curso; esto es solo para demostrar
una llamada de función con un evento generado por el usuario.
o simplificado aún más (la función tiene solo una sentencia, cuyo valor regresa):
Si la función flecha toma exactamente un parámetro, se pueden omitir los paréntesis. Volvamos a los
ejemplos con la función recursiva factorial, que toma exactamente un parámetro, n. En el ejemplo
anterior, lo declaramos usando la sentencia de función que ya hemos empleado:
function factorial(n) {
return n > 1 ? n * factorial(n - 1) : 1;
}
console.log(factorial(5)); // -> 120
Usando la expresión de la función flecha, podemos escribirla en una forma aún más compacta. Toma en
cuenta que esta vez, el parámetro no se proporciona entre paréntesis (nuevamente, si la función flecha
toma exactamente un parámetro, se pueden omitir los paréntesis). Dado que la función devuelve el
resultado de exactamente una instrucción, también se puede omitir la palabra clave return.
La expresión flecha se usa principalmente para funciones cortas, a menudo anónimas, que pueden
presentarse como aún más compactas en esta forma. Se diferencian de las funciones ordinarias por una
cosa más aparte de la forma de notación, en otras palabras, cómo se interpreta la palabra
clave this dentro de ellas. Sin embargo, este es un tema relacionado con la programación orientada a
objetos más avanzada, que está mucho más allá del alcance de este curso. Por lo tanto, podemos suponer
que ambas formas de definir funciones, es decir, expresión de función y expresión de función flecha, te
permiten definir funciones que funcionan de manera idéntica.
Un ejemplo típico del uso de funciones flecha es el método forEach, disponible en datos de
tipo Array. Hemos aprendido varias formas de pasar a través de los elementos del arreglo, utilizando
diferentes tipos de bucles. El método forEach es otro, y francamente hablando, actualmente el más
utilizado. Este método toma como argumento... una función.
Esta función se llamará cada vez para cada elemento del arreglo. Podemos crear cualquier función para
este propósito. Hay una condición, que debe tener al menos un parámetro, que será tratado como un
elemento visitado del arreglo (la sintaxis de esta función puede ser un poco más compleja, pero la
explicaremos en la siguiente parte del curso). Veamos un ejemplo sencillo:
La función showName se ha pasado como argumento de llamada al método forEach del arreglo
names. Por lo tanto, showName será llamado tres veces, por cada elemento del arreglo de names, y en
cada llamada su parámetro será igual al nombre sucesivo, es decir a su vez: Alice , Eva y Juan. Lo
único que tiene que hacer showName es mostrar el element (name) recibido.
Se puede lograr el mismo efecto pasando una función flecha anónima al método forEach. Ni siquiera lo
almacenamos en una variable, porque asumimos que la usaremos solo aquí y no la volveremos a
consultar.
Resumen
Las funciones son uno de los elementos más fundamentales de la programación en la mayoría
de los lenguajes y es difícil imaginar escribir un programa sin ellas. En nuestro curso, los
hemos estado usando desde el principio; después de todo, el método log del objeto de la
consola (que es simplemente console.log) también es una función. Entonces usamos
funciones creadas y proporcionadas por otros programadores. En el caso de console.log, en
realidad es una parte integral del lenguaje provisto como una función, pero hay muchas
funciones que brindan alguna funcionalidad nueva y están escritas por terceros (empresas,
organizaciones o desarrolladores privados). Estas funciones suelen estar disponibles en forma
de bibliotecas (es decir, conjuntos de funciones), que se dedican a resolver una clase
específica de problemas, por ejemplo, Leaflet (mapas), D3.js (visualización de datos) o jQuery
(manipulación DOM).
Una cosa es usar las funciones listas para usar, pero otra muy distinta es escribirlas para tus
propias necesidades. Las funciones permiten, entre otras cosas, una división lógica más
sencilla del programa, reutilizando el mismo código y probando partes seleccionadas del
mismo. En este capítulo, se te ha familiarizado con diferentes métodos para definir funciones
(declaración clásica usando la declaración de función, expresiones de función o expresiones
de función flecha), pasándoles parámetros y devolviendo valores de ellos. Has visto la
diferencia entre funciones nombradas y anónimas, se te ha familiarizado con el concepto de
funciones callback y has visto cómo entendemos la recursividad en una función. Entonces, si
es necesario, podrás no solo escribir tu propia función, sino también personalizar la forma en
que la defines y la usas. Por supuesto, este capítulo no agota el tema de las funciones en
JavaScript, pero es una buena base para ejercicios posteriores que te permitirán comenzar a
usar funciones mientras programas.
Errores y excepciones
Es muy importante que te prepares para esta simple verdad:
Ciertamente has sido testigo, en múltiples ocasiones, de diferentes aplicaciones que funcionan mal, se
vuelven inestables, arrojan resultados inesperados o incluso se apagan sin control. Desafortunadamente,
nosotros, los programadores, somos responsables de la mayoría de estos problemas. Incluso si no
causamos estos problemas directamente, probablemente no anticipamos ciertas situaciones que podrían
conducir a un mal funcionamiento del programa (por ejemplo, falta de conexión a la red).
Ocurrirán errores en el funcionamiento de los programas. Tenemos que aceptar eso, mientras tratamos
de minimizar su número y mitigar el daño que potencialmente pueden causar. Podemos cometer errores
en cada etapa del desarrollo de software, desde un diseño incorrecto hasta errores tipográficos comunes
en el código que escribimos. Los errores serán el resultado de una concepción errónea al tratar de
resolver un determinado problema, el mal uso del lenguaje de programación, o la incapacidad de
predecir comportamientos extraños del usuario. Desafortunadamente, los errores en el código que causan
errores son una parte integral de la programación.
Este hecho se expresa mejor con las palabras de uno de los fundadores de la informática moderna,
Edsger W. Dijkstra: "Si la depuración es el proceso de eliminar errores de software, entonces la
programación debe ser el proceso de colocarlos".
Lenguajes naturales y errores de comunicación
Los lenguajes de programación no se llaman lenguajes por casualidad. Al igual que los lenguajes
naturales, los lenguajes que usamos para comunicarnos con otras personas, los lenguajes de
programación se usan para formular con precisión sentencias (instrucciones) interpretables sin
ambigüedades. Y al igual que los lenguajes naturales, tienen su gramática y vocabulario.
Cada lenguaje también tiene su propio vocabulario limitado, que es una lista de palabras que se pueden
usar para construir instrucciones (es decir, nuevamente, las oraciones de un lenguaje natural). Esta
imagen está bastante simplificada, pero debería permitirnos comprender qué errores pueden ocurrir al
usar el lenguaje. Para comenzar, intentaremos presentar la diferencia entre varias categorías de errores
usando un lenguaje natural.
Esta es una oración que podemos tratar como una instrucción, que describe sin ambigüedades un
determinado procedimiento. ¿Qué pasaría si nos apresuráramos al escribir esta información?
Comencemos con los signos de puntuación que faltan:
Éste es un ejemplo de un error de sintaxis (o más precisamente, errores). En español, una oración
declarativa debe terminar con un punto. Probablemente la persona que reciba este mensaje adivine
fácilmente de qué se trata, pero formalmente será incorrecto y ambiguo. El intérprete (p.ej. el motor de
JavaScript) o el compilador no pueden adivinar el significado de lo que hemos escrito. Si se produce un
error de este tipo, será necesario que lo corrijamos. Dichos errores suelen ser muy fáciles de detectar
automáticamente y siempre deben corregirse. Violan las reglas de la sintaxis del lenguaje. El programa
no se ejecutará si contiene un error de sintaxis.
Recuperemos los signos de puntuación, pero cambiemos una de las palabras, reemplazando "camino"
con "cmno".
Nuevamente, el destinatario probablemente adivinará de qué se trata, pero el intérprete no puede darse el
lujo de adivinar qué significa la palabra "cmno", porque no conoce el significado de esa palabra. Tal
error también es fácil de detectar, porque la palabra "cmno" no está en el vocabulario de nuestro
lenguaje. Se trata de un error semántico. En lenguajes de programación compilados, este tipo de error
no permitirá la compilación y ejecución del programa. En JavaScript, el intérprete iniciará el programa y
detendrá su ejecución después de llegar a dicha instrucción. Este tipo específico de error semántico en
JavaScript se denomina error de referencia.
¿Qué sucede si reemplazamos una palabra por un error tipográfico con una que existe en nuestro
diccionario?
Esta vez hemos cambiado la palabra "primero" por la palabra "primitivo". Si una persona analiza la
oración, sentirá que algo está mal y comenzará a buscar un error: la palabra "primitivo" no coincide en
absoluto con la oración, y probablemente adivinará con qué reemplazarla. Este tipo de error ya no será
tan fácil de detectar para un intérprete. La palabra está en el vocabulario, y el análisis tiene que hacerse
en un contexto más amplio. También es un error semántico.
La última categoría son los errores lógicos. Son, por mucho, los más difíciles de encontrar, porque desde
un punto de vista formal, todo parecerá correcto. Deberíamos decirle a nuestro amigo que gire a la
derecha, pero ocupado con otra cosa nos apresuramos a escribir... a la izquierda.
Formalmente, todo parece correcto: sintaxis, vocabulario, contexto. La información es consistente y sin
ambigüedades, pero obviamente incorrecto. El error no se detectará hasta que alguien intente seguir esta
instrucción y desaparezca en algún lugar del desierto.
Los dos últimos errores pueden parecer bastante similares a primera vista, pero describen dos situaciones
completamente diferentes. Un error lógico hace posible ejecutar la instrucción, pero dará un resultado
erróneo. Una instrucción con un error semántico no tendrá sentido, por lo que lo más probable es que no
sea posible ejecutarla de esta forma.
Esta vez, tenemos un error tipográfico en el nombre de la función declarada: en vez de multiply,
hemos escrito multipl. En la llamada de función, usamos el nombre multiply, que no existe. Esto es
un error semántico, en este caso fácil de detectar, porque no existe ninguna función con este nombre. La
ejecución del programa se interrumpe en la línea del error. Presta atención a dos cosas. En primer lugar,
los mensajes de error que se muestran en la consola determinan con bastante precisión qué y dónde algo
falla; lee esta información detenidamente, ya que ayudará a eliminar el error. La segunda cosa es el
comienzo del mensaje: Uncaught .... Si un error puede no contenerse, probablemente se pueda
contenter. Y efectivamente se puede, como veremos en un momento.
Cuando JavaScript detecta errores sintácticos o semánticos, genera y arroja objetos específicos que
contienen información sobre el error encontrado. Por lo general, en tal situación, decimos que se ha
producido un error. En el caso de errores de sintaxis, el motor de JavaScript no nos permite ejecutar el
programa, y en la consola recibimos información sobre lo que es incorrecto. Los errores que no sean de
sintaxis (por ejemplo, errores semánticos) generalmente se denominan errores de tiempo de
ejecución en JavaScript. Aparecen mientras se ejecuta el programa. También podemos
llamarlas excepciones. De forma predeterminada, las excepciones lanzadas interrumpen la ejecución del
programa y hacen que aparezca la información adecuada en la consola (observamos esto en nuestro
ejemplo con la función multiply). Generemos de nuevo la situación errónea:
try {
console.log('abc'); // -> abc
conole.log('abc');
} catch (error) {
console.log(error.message); // -> conole is not defined
}
Si se lanza una excepción en el bloque de código después de la palabra clave try, el programa no se
interrumpe por completo, sino que salta a la parte del código después de la palabra clave catch y
continúa desde allí. Echaremos un vistazo más de cerca a esta construcción en breve.
La conclusión es bastante simple: si estás aprendiendo sobre una nueva función u operador,
debes verificar en la documentación (por ejemplo, en la página de MDN) cómo se comportan
en caso de errores. Algunos de ellos generarán excepciones, mientras que otros devolverán
algunos valores específicos. Dependiendo de eso, podrás prepararte adecuadamente para
manejar errores utilizando el método try o instrucciones condicionales simples. Por cierto, para
los ejemplos que se acaban de mostrar, la solución más sensata sería comprobar si los
valores proporcionados realmente son números (¿recuerdas el operador typeof?).
Confianza Limitada
Los programas no se ejecutan en el vacío. Por lo general, durante su ejecución, hay
interacciones con los usuarios (p. ej., ingresar datos necesarios para calcular ciertos valores)
u otros programas o sistemas (p. ej., descargar datos del servidor). El comportamiento tanto
de los usuarios como de otros sistemas debe tratarse con precaución, y no podemos asumir
que el usuario proporcionará datos en el formato que requerimos, o que el servidor de datos
siempre funcionará. Tales situaciones inesperadas también serán fuentes de errores en
nuestro programa. Y aunque no dependen directamente de nosotros, es nuestra
responsabilidad anticiparnos a situaciones potencialmente peligrosas. Si, por ejemplo,
escribimos una calculadora en la que el usuario ingresa sus valores, entonces probablemente
deberíamos verificar si el divisor no es un cero antes de hacer la división. En teoría, el usuario
debe saber que no dividimos entre cero, pero somos responsables de la estabilidad del
programa. No le creas al usuario ni a otros sistemas. Debes suponer qué puede salir mal y
verifica los datos recibidos antes de usarlos en tu programa.
Vamos a escribir un fragmento de código que te pedirá que introduzcas dos números. Luego
queremos mostrar el resultado de dividir el primer número entre el segundo:
Existen algunos tipos de errores subyacentes que produce JavaScript. La mayoría de las
veces, especialmente al principio, encontrarás errores de sintaxis y de referencia. También
discutiremos los errores de tipo y rango.
1. SyntaxError
Como dijimos anteriormente, un SyntaxError aparece cuando un código está mal formado,
cuando hay errores tipográficos en las palabras clave, paréntesis o corchetes que no
coinciden, etc. El código ni siquiera se puede ejecutar, ya que JavaScript no es capaz de
entenderlo. Por lo tanto, se lanza el error correspondiente antes de que se inicie el programa.
"use strict";
console.log("true");
2. ReferenceError
Ya hemos visto este error. Ocurre cuando intentamos acceder a una función o variable que no
existe. El motor de JavaScript no conoce el significado del nombre dado, por lo que es un
error que calificaremos como un error semántico. La excepción correspondiente se lanza en el
momento de la ejecución del programa, cuando se alcanza la instrucción incorrecta (es decir,
en JavaScript, los errores semánticos son errores de tiempo de ejecución).
Esta vez, hemos intentado llamar la función fun. Si no la hemos declarado antes, y no hay
ninguna función con este nombre entre las funciones estándar de JavaScript, la llamada
finaliza con un error.
3. TypeError
Este tipo de error se produce cuando un determinado valor no es del tipo esperado (es decir,
intenta realizar una operación que no es aceptable). Los ejemplos típicos son cambiar el valor
constante o verificar la longitud de una variable que no es una cadena. Este error es
particularmente importante cuando se trabaja con objetos, lo cual está fuera del alcance de
este curso (hablaremos de ellos en la siguiente parte del curso). Este es un run-time
error típico, por lo que se lanzará la excepción apropiada mientras se ejecuta el programa,
después de llegar a la instrucción problemática.
const someConstValue = 5;
someConstValue = 7; // -> Uncaught TypeError: Assignment to constant
variable.
Intentar almacenar el nuevo valor en la constante someConstValue falló por razones obvias, lo
que resultó en un TypeError.
Esta vez, hemos intentado tratar el contenido de la variable someNumber como una cadena y
verificar su longitud. El motor de JavaScript nota que la variable almacena un número, y tal
operación no está permitida.
4. RangeError
Este tipo de error se genera cuando pasas un valor a una función que está fuera de su rango
aceptable.
Nuevamente, es un run-time error, y la excepción se lanza mientras el programa se está
ejecutando, después de llegar a la instrucción incorrecta. De hecho, esta excepción es más
útil al escribir tus propias funciones y manejar errores. Luego puedes lanzar una excepción en
ciertas situaciones.
console.log(testArray1.length); // -> 10
console.log(testArray2.length);
En el ejemplo, hemos intentado crear dos arreglos usando el constructor (es decir, la función
predeterminada) Array . Si pasamos un argumento a esta función, se tratará como el tamaño
del arreglo recién creado. El primer arreglo ( testArray1 ) se crea sin ningún problema. Como
puedes adivinar, falla la creación del arreglo testArray2 con una longitud negativa.
5. Otros errores
Existen algunos tipos de errores más: EvalError , InternalError y URIError , pero son
bastante raros, regresaremos a ellos si es necesario.
try {
//codigo a probar
} catch (error) {
La premisa básica es simple: si tenemos un fragmento de código que posiblemente pueda salir mal,
podemos incluirlo en la cláusula try . JavaScript intentará ejecutar este código, y si ocurre algún error y
se lanza una excepción, se ejecutará el código dentro del bloque catch ; si el código try se ejecuta sin
errores, entonces se ignora el bloque catch . Después de ejecutar los comandos del bloque catch , el
programa continuará ejecutándose desde la primera instrucción fuera de la instrucción try ...
catch .
Toma en cuenta que la palabra clave catch va seguida de paréntesis que contienen el error de
parámetro. Este es un nombre de variable que contendrá información sobre el error que se detectó, y
puede tener cualquier nombre válido, excepto los nombres error , err o simplemente e , son de uso
común. En el caso de una excepción lanzada por JavaScript, el objeto de error contendrá información
sobre el tipo de error y se convierte en una cadena para ser registrada o procesada de cualquier forma
que necesite el desarrollador.
Entonces, modifiquemos el código que vimos anteriormente, y que sabemos con seguridad arroja
errores:
try {
let a = b;
} catch (error) {
La sentencia que produce ReferenceError ahora está dentro del bloque try . El resultado es que
nuestro código ya no se detiene por errores. Y podemos reaccionar en el bloque catch. En este ejemplo,
registramos un mensaje sobre el error. El primer error que se arroje en el bloque try siempre se
detectará, la ejecución saltará al bloque catch y no habrá más errores en el bloque try el bloque será
atrapado. Lo importante es que después de salir del bloque catch , el programa seguirá funcionando con
normalidad (en nuestro caso escribirá "Hemos manejado la excepción" en la consola).
Toma en cuenta que try...catch no funcionará en un SyntaxError . Esto no debería ser una
sorpresa para ti. Como hemos dicho varias veces antes, si el motor JavaScript detecta un error de
sintaxis, no te permitirá ejecutar el programa. Si el programa no se ha ejecutado, es bastante difícil
imaginar que pueda reaccionar de alguna manera a lo que ha sucedido.
Tarea
Reescriba todos los ejemplos de este capítulo de tal manera que los errores sean capturados por una
sentencia try...catch .
Manejo de excepciones condicionales
A veces queremos poder reaccionar de manera diferente a tipos específicos de errores dentro
del bloque catch. Podemos hacer esto usando el operador instanceof. Hablaremos del
operador en sí más adelante, porque es un tema bastante complicado. Por ahora, es
suficiente saber cómo podemos usarlo al manejar errores. La sintaxis del
operador instanceof tiene este aspecto:
Si, por ejemplo, recibimos un error en el bloque catch (pasado como argumento de error),
podemos comprobar si es del tipo ReferenceError de la siguiente manera :
En este ejemplo, podemos ver cómo podemos reaccionar de una manera específica solo al
tipo de error seleccionado:
let a = -2;
try {
a = b;
} catch (error) {
if (error instanceof ReferenceError) {
console.log("Reference error, restablecer a a -2"); // ->
Reference error, restablecer a a -2
a = -2;
} else {
console.log("Otro error - " + error);
}
}
console.log(a); // -> -2
Es importante saber que cualquier variable que se declare usando la palabra clave let dentro
de un bloque try no es accesible en el bloque catch (ni en el bloque finally, que se
discutirá en un momento). Si no estás seguro de por qué es así, vuelve por un momento al
capítulo sobre declaraciones de variables y su alcance de visibilidad.
La sentencia finally
El último bloque opcional de la sentencia try es el bloque finally. Se puede usar con o sin
el bloque catch, y siempre se ejecuta después de los bloques try y catch,
independientemente de que se produzca algún error. La sintaxis para try ... finally se
ve así:
try {
// código a probar
} finally {
// esto siempre se ejecutará
}
let a = 10;
try {
a = 5;
} finally {
console.log(a); // -> 5
}
console.log(a); // -> 5
let a = 10;
try {
a = b; // ReferenceError
} finally {
console.log(a); // -> 10
}
console.log(a);
El bloque finally también se puede usar junto con el bloque catch, ya que ambos son
opcionales, pero al menos uno de ellos es requerido por try, y si ninguno de ellos está
presente, se lanza un SyntaxError.
let a = 10;
try {
a = b; // ReferenceError
} catch (error) {
console.log("¡Un error!"); // -> ¡Un error!
} finally {
console.log("¡Finalmente!"); // -> ¡Finalmente!
}
console.log(a); // -> 10
En este caso, la excepción provoca un salto al bloque catch, luego al bloque finally.
Luego, el programa continúa funcionando fuera de la sentencia try...catch.
let a = 10;
try {
a = b; // ReferenceError
} catch (error) {
console.log("¡Un error!");
}
console.log("¡Finalmente!");
Este código tendrá un resultado similar al del ejemplo anterior: registrará ¡Un error! y
luego ¡Finalmente!. Es cierto que en este sencillo ejemplo, ambos scripts se comportarán
igual, pero hay ligeras diferencias, y la más importante es que el bloque finally se ejecutará
incluso cuando se arroje un error desde el bloque catch.
let a = 10;
try {
a = b; // Primer ReferenceError
} catch (error) {
console.log(b); // Segundo ReferenceError
}
console.log("¡Finalmente!");
Ahora la última llamada a console.log nunca se ejecutará, ya que se lanza otro error (esta
vez no detectado) en el bloque catch. Esto no sucederá si usamos el bloque finally así:
let a = 10;
try {
a = b; // Primer ReferenceError
} catch (error) {
console.log(b); // Segundo ReferenceError
} finally {
console.log("¡Finalmente!");
}
Ahora se ejecutará la llamada console.log del bloque finally, aunque esto no cambia el
hecho de que la ejecución del programa se detendrá en este segundo ReferenceError, ya
que no se captura.
Los bloques try...catch...finally se pueden anidar, por lo que podemos usar un bloque
completo try...catch dentro de otro bloque try...catch. Esto es útil cuando esperamos
que ocurran múltiples errores y necesitamos manejarlos todos.
let a = 10;
try {
a = b; // Primer ReferenceError
} catch (error) {
try {
console.log(b); // Segundo ReferenceError
} catch {
console.log("¡Segundo catch!"); // -> ¡Segundo catch!
}
} finally {
console.log("¡Finalmente!"); // -> ¡Finalmente!
}
En este ejemplo, detectamos la excepción dentro del bloque catch colocando el código
dentro de otra instrucción try...catch.
Para lanzar una excepción, usamos la instrucción throw. Le sigue cualquier valor que será
tratado como una excepción. Puede ser, por ejemplo, un número o uno de los objetos de error
preparados (por ejemplo, RangeError).
Una excepción que lancemos hará que el programa reaccione de la misma manera que las
excepciones de JavaScript originales (es decir, detendrá su ejecución). Es decir, a menos que
lo arrojemos dentro del bloque try para manejarlo. Veamos un ejemplo sencillo, sin tratar de
encontrarle ningún significado especial. Esta es solo una ilustración del uso de la
instrucción throw:
Una excepción no admitida (si el número 100 se puede llamar una excepción) hace que el
programa se detenga. La segunda instrucción console.log nunca se ejecuta.
Soñamos con una función que nos permita contar factoriales (espero que todavía recuerdes lo que es un
factorial de tus lecciones de matemáticas; si tienes dudas, echa un vistazo rápido a Wikipedia para ver un
ejemplo). Lo escribiremos en una versión iterativa, es decir, usando un bucle. No será la solución más
elegante ni la más óptima, sino simple y legible.
function factorial(n) {
let result = 1;
for (; n > 1; n--) {
result = result * n;
}
return result;
}
console.log(factorial(3)); // -> 6
console.log(factorial(5)); // -> 120
console.log(factorial(8)); // -> 40320
console.log(factorial(20)); // -> 2432902008176640000
console.log(factorial(1000)); // -> Infinity
Digamos que estamos un poco asustados por los grandes números que devuelve nuestra función,
especialmente el valor Infinity, por lo que decidimos limitar el rango máximo de valores admitidos.
No aceptaremos argumentos mayores de 20.
function factorial(n) {
if (n > 20) {
throw new RangeError("Valor máximo 20");
}
let result = 1;
for (; n > 1; n--) {
result = result * n;
}
return result;
}
La presencia de una instrucción condicional dentro de nuestra función es bastante obvia. También lo es
el uso de la instrucción throw. La construcción new RangeError("Valor máximo 20")
definitivamente necesita una explicación. Está un poco fuera del alcance de esta parte del curso, por lo
que intentaremos explicarlo de la manera más simple posible, centrándonos solo en su lado funcional.
Como mencionamos anteriormente, la instrucción throw puede tomar cualquier valor. Anteriormente,
usábamos un número simple, pero esta vez buscamos algo más complejo. Es un objeto, que es un tipo de
dato compuesto. Puede crear un nuevo objeto de muchas maneras, incluso mediante el uso del operador
new. Usando este operador, creamos un objeto de clase RangeError, que es un error predefinido que
discutimos hace un tiempo. El nuevo objeto es iniciado por la cadena Valor máximo 20. Y tal nuevo
objeto, del tipo RangeError, que contiene, entre otras cosas, la cadena que proporcionamos, será
lanzado si el parámetro n excede el valor permitido.
Resumen
Ocurrirán errores, pero cuando un desarrollador los acepta, puede escribir código que estará
listo para la mayoría de ellos. Es una buena práctica prepararse siempre para los problemas y
también ejecutar el código con la mayor frecuencia posible, ya que esto minimizará la cantidad
de código nuevo que potencialmente puede introducir nuevos errores. Trata de recordar
algunas cosas básicas:
Tareas
Tarea 1
Escribe tu propia función div que tomará dos argumentos de llamada y devolverá el resultado
de dividir el primer argumento entre el segundo. En JavaScript, el resultado de dividir entre
cero es el valor Infinity (o -Infinity, si intentamos dividir un número negativo). Cambia
esto. Si se pasa 0 como segundo argumento, tu función debería lanzar una
excepción RangeError con el mensaje apropiado. Prepara una llamada de prueba de la
función tanto para la división válida como para la división entre cero.
Ejemplo
function div(a, b) {
if (b == 0) {
return a / b;
Tarea 2
Escribe un programa que, en un bucle, divida el número 1000 entre elementos sucesivos del
arreglo de números, mostrando el resultado de cada división. Para dividir los números, usa la
función de la tarea anterior. Usa la sentencia try...catch para manejar una excepción
lanzada en el caso de la división entre cero. Si se detecta una excepción de este tipo, el
programa debe imprimir un mensaje apropiado (tomado de la excepción) y continuar su
operación (división por elementos sucesivos del arreglo).
Ejemplo
let result;
try {
} catch (e) {
result = e.message;
console.log(result);
Vemos una función que calcula el promedio de dos números y devuelve el resultado. La función
parece simple: suma dos números dados y los divide entre 2. Este código tiene una sintaxis
válida, no tiene problemas formales y esperamos que los resultados de las dos llamadas en el
ejemplo sean 6 y 5. Pero cuando ejecutamos este código, los resultados son muy diferentes.
¿Puedes ver dónde está el error?
return a + b / 2;
Esto no funciona como se esperaba (dividir la suma de dos números entre 2) debido al orden de
las operaciones. La división b / 2 se calcula primero, luego se le suma a, por lo que este
código es el mismo que este:
return a + (b / 2);
Vemos una función que debería devolver el mayor de tres números. La idea de cómo resolver
este problema es simple: cuando la variable a es mayor que b y c, a es el número más grande.
Si este no es el caso, entonces si b es mayor que a y c, b es el mayor número. Si ninguno es
cierto, eso significa que c es el número más grande. Toma tu tiempo y trata de detectar la falla
en esta lógica. Como sugerencia, intenta llamar a la función con estos conjuntos de parámetros:
console.log(largest(1, 1, 2)); // -> 2
console.log(largest(1, 2, 3)); // -> 3
console.log(largest(3, 2, 1)); // -> 3
console.log(largest(2, 2, 1)); // -> 1
Esto es realmente útil cuando sospechamos que el comportamiento o la lógica del programa es
defectuosa y el código va a una rama de ejecución incorrecta (va a la sentencia if incorrecta, etc.). En
este modo, podemos ver cada línea que se ejecuta y cada línea que no. Podemos ver fácilmente si la
lógica en las sentencias de control de flujo es válida o no.
Ya sabemos que la sentencia debugger, cuando JavaScript la encuentre, detendrá la ejecución del
código en ese lugar. Dependiendo del navegador que estemos usando, los botones de control de flujo
pueden verse diferentes y pueden estar ubicados en diferentes lugares. En general, todos los navegadores
modernos admiten las siguientes opciones para controlar la ejecución del script en modo de depuración:
Reanudar/Continuar. Esto reanudará la ejecución del script de forma normal y se usa cuando
hemos verificado lo que queríamos verificar y ahora queremos continuar con la ejecución del
script.
Step Into. Esto ejecuta la siguiente instrucción en el código solamente, y lo pausa de nuevo, y
lo usamos cuando queremos analizar el código en detalle, o verificar qué ruta exacta toma la
ejecución cuando ocurre una bifurcación compleja debido a las sentencias if. ..else u otra
lógica complicada. Si la siguiente instrucción es una llamada de función, usar Step Into saltará
dentro del código de esta función.
Step Over. Esto funciona como Step Into, excepto que si se usa cuando la siguiente instrucción
es una llamada de función, el código no saltará al código de función, pero se ejecutará toda la
función, y el programa se pausará nuevamente después de salir de esta función. Esto se usa a
menudo cuando la siguiente instrucción es una llamada a una función en la que no sabemos si
tendrá algún impacto, o simplemente no estamos interesados en buscar.
Step Out. Esto nos permite salir inmediatamente de una función en la que el código está en
pausa.
Intentemos practicar algunas acciones básicas que se pueden realizar con el depurador. El programa
JavaScript que vamos a depurar debe reescribirse en su entorno de desarrollo local (por alguna razón, la
depuración es más legible si no usamos la plataforma OpenEDG en estos ejercicios). ¿Recuerdas cómo
puedes hacer algo como esto? Al comienzo del curso, ejecutamos nuestro código abriendo un archivo
HTML simple en el navegador, que incluía una referencia al archivo JavaScript que se ejecutaría (el
capítulo titulado "El Programa ¡Hola, Mundo!").
<!DOCTYPE html>
<html>
<head>
<script src="main.js"></script>
</head>
<body>
<p>Sitio de Prueba</p>
</body>
</html>
Guarde el archivo en tu disco local, preferiblemente en un directorio vacío recién creado. En el mismo
directorio, guarda el archivo main.js (al cual, como habrás notado, se hace referencia en el
código index.html), colocando en su interior el siguiente código:
function outer() {
let name = "outer";
let str = inner();
return str;
}
function inner() {
let name = "inner";
return "¡Hola!";
}
En el navegador que estás utilizando, abre una nueva pestaña y carga el archivo index.html.
Dependiendo de tu navegador y sistema, puedes usar el menú del programa o el atajo de teclado
apropiado (en Linux y Windows: Ctrl + O, en macOS: CMD + O). Si todo se ha hecho correctamente,
verás este texto en la pestaña: "Sitio de Prueba".
Ahora necesitamos iniciar las herramientas de desarrollo. Discutimos cómo ejecutarlos en diferentes
sistemas y navegadores en el capítulo titulado "Herramientas de Desarrollo". Por ejemplo, en los
navegadores Chrome y Firefox, en Windows y Linux, usamos la combinación de teclas: Ctrl + Shift + I.
En el resto de este ejercicio, nos limitaremos a discutir cómo funciona el depurador usando los
navegadores Chrome y Firefox como ejemplos.
Este es el resultado de los métodos console.log del programa escrito en el archivo main.js. Si todo ha
funcionado hasta ahora, estamos listos para comenzar a jugar con la depuración.
Uso de la sentencia debugger
Probemos la sentencia debugger en la práctica. Colócala en el código main.js antes de
llamar a la función outer. Entonces, las últimas líneas del archivo main.js ahora deberían
verse así:
Usar Reanudar o Resume no necesariamente hace que el programa se ejecute por completo. Podemos
indicar dónde debe detenerse nuevamente. Vuelve a cargar la página. Observa que el depurador muestra
números de línea a la izquierda del código. Haz clic en el número 15, que indica la última línea de
nuestro código. Así es como establecemos el punto de interrupción (la línea se resaltará). Haz clic en el
número de línea nuevamente si deseas eliminar el punto de interrupción (no lo elimines todavía). Si
ahora hacemos clic en el botón Reanudar o Resume (o usamos F8), el programa continuará y se detendrá
en el punto de interrupción. Como resultado, la consola mostrará el siguiente texto:
Para ser honesto, al depurar código, rara vez usamos la sentencia debugger. La mayoría de las veces, en
el lugar donde el programa debe detenerse, simplemente lo indicamos usando puntos de interrupción
establecidos directamente en las Herramientas para Desarrolladores. Antes de seguir trabajando, elimina
los puntos de interrupción (haciendo clic en los números de línea apropiados).
Cómo lidiar sin la sentencia debugger
Nuevamente, modifica el programa guardado en main.js, esta vez eliminando la línea que contiene el
comando debugger. Guarda los cambios, vuelve a tu navegador y vuelva a cargar la página. Obviamente,
el programa se ha ejecutado hasta el final, pero ahora sabemos cómo detenerlo. Establece dos puntos de
interrupción, uno en console.log("Antes de llamar a outer()"); el otro
en console.log("Después de llamar a outer()"); (ahora estas deberían ser las líneas 12 y
14 respectivamente). Vuelve a cargar la página. El programa debe detenerse en el primer punto de
interrupción. Al hacer clic en Reanudar o Resume, el programa reanudará la ejecución y se detendrá en
el segundo punto de interrupción.
Otro clic en Reanudar o Resume hará que el programa se ejecute hasta el final.
Presiona Step Over dos veces más (como alternativa, usa el atajo F10) observando los cambios
en la consola y el resaltado del código.
Step into
Veamos cuál es la diferencia entre Step Over y Step Into en la práctica. Deja la configuración
del punto de interrupción sin cambios y vuelve a cargar la página. Primero ejecuta Step
Over (presiona el botón o el atajo F10). Luego, cuando nos detengamos en la
línea console.log(outer()), ejecuta Step Into.
¿Qué sucede? Esta vez, el depurador trata la función outer como un conjunto de instrucciones,
salta dentro de ella y se establece en su primera línea. Usando Step Into, avanza más en la
función inner y deténte en la línea return "¡Hola!".
Call Stack
Este es un buen momento para echar un vistazo a otro elemento del depurador: el Call Stack o Pila de
Llamadas. En una ventana con ese nombre, podemos ver en qué función nos encontramos actualmente
(en nuestro caso, inner ). Es más, allí veremos todas las funciones que están actualmente activas. La
pila de llamadas es importante en el caso de funciones anidadas, como en nuestro ejemplo. Usando Step
Into, llamamos a la función outer en el programa principal, ingresamos y llamamos a la función inner. Si
nos detenemos dentro de la función inner, entonces las funciones activas serán: inner y outer (creando
una pila). En la parte inferior de la pila, veremos la función principal (no tiene nombre, y en Firefox está
marcada como (global) y en Chrome (anónima)). Este es el lugar desde donde se llama a la función
outer.
Nos detenemos en la línea 9, dentro de la función inner, en el comando return "¡Hola!". Así que estamos
en el contexto de la función inner en este punto. En la consola en la parte inferior de la pantalla, escribe
el comando:
Como resultado de su ejecución, debería mostrarse el nombre "inner" (es decir, el contenido del nombre
de la variable local de la función inner). Si hace clic en el nombre de la función outer en la pila de
llamadas, serás llevado al contexto de esa función (toma en cuenta que la selección de la línea actual ha
cambiado). Intenta llamar al mismo comando nuevamente en la consola:
Esta vez deberíamos ver "outer". Estamos en el contexto de la función outer, que tiene su propia variable
local llamada name. Esta variable contiene la palabra "outer". Vuelve a hacer clic en el nombre de la
función inner en la Call Stack o Pila de Llamadas para volver a cambiar el contexto. Toma en cuenta
que a pesar del cambio de contexto, la ejecución del programa aún se detiene exactamente en el mismo
lugar.
Durante la ejecución paso a paso, tenemos libre acceso a las variables de nuestro programa, las cuales
son visibles en el contexto en el que nos encontramos actualmente. Como acabamos de ver, usando el
método console.log podemos escribir los valores de tales variables. También podemos modificarlos sin
ningún problema.
Como puede ver, hemos modificado el valor de la variable local name, que se encuentra en la función
inner. Si continuamos la ejecución del programa (Step o Resume), el programa utilizará este nuevo
valor.
Elimina todos los puntos de interrupción y establece uno nuevo en la línea 8, dentro de la función inner.
Vuelve a cargar el programa y la ejecución debería detenerse en la línea que hemos marcado. Presionar
el botón Step Out ejecutará el resto de las instrucciones en la función inner y se detendrá en la primera
línea después de llamarla (dentro de la función outer). Sencillo, ¿verdad?
El ejemplo que usamos realmente no requiere depuración. Es solo para demostrarte las funciones básicas
del depurador. Las necesitarás si el programa que vas a escribir se comporta de manera inconsistente en
relación con tus expectativas.
El requisito básico que imponemos a los programas es, por supuesto, que funcionen correctamente. Su
funcionamiento debe ser predecible y coherente con nuestras suposiciones, y los resultados que arrojan
deben ser correctos. Sin embargo, la eficiencia del programa a menudo también es importante. A veces,
el mismo efecto se puede lograr de varias maneras, por lo que vale la pena elegir la que funcionará no
solo correctamente sino también rápidamente.
En los ejemplos discutidos hasta ahora en el curso, la velocidad ha tenido una importancia marginal. Los
programas eran simples, realizaban operaciones sin complicaciones y estas operaciones solían ser muy
pocas. Sin embargo, el aspecto de la velocidad de ejecución del código es bastante importante. Se ve
afectado por muchos elementos, como la elección de un algoritmo óptimo para resolver un problema
determinado, la selección de funciones apropiadas o evitar acciones redundantes.
Una de las formas más sencillas de medir la velocidad del programa es utilizar los
métodos console.time y console.timeEnd , que nos permiten realizar una medición precisa del
tiempo entre dos lugares especificados en nuestro código, y mostrar el resultado en la consola. Por
supuesto, existen muchas más herramientas avanzadas, que pueden ayudarnos durante la optimización
de nuestro código, pero vale la pena conocer estos métodos simples, que en muchos casos son
suficientes para analizar el rendimiento del programa.
El valor de pi calculado de esta manera es aproximado, pero es más preciso cuanto más larga es la serie
(es decir, cuanto mayor es el valor de k que usamos). Por supuesto, una mayor precisión implicará un
mayor número de operaciones a realizar y, por lo tanto, afectará el tiempo de ejecución del programa.
Veamos cómo se vería un programa de ejemplo escrito en JavaScript, que nos permitiría realizar tales
cálculos:
let part = 0;
for (let k = 0; k < 10000000; k++) {
part = part + ((-1) ** k) / (2 * k + 1);
}
let pi = part * 4;
console.log(pi); // -> 3.1415925535897915
La parte variable contendrá un resultado parcial, que se modificará en cada iteración del bucle for. El
bucle se ejecutará diez millones de veces (es decir, k tomará valores de 0 a 999999). La parte que más
tiempo consumirá será la ejecución del bucle for, porque en cada iteración se realizan operaciones como
sumar, multiplicar, dividir y exponenciación.
Veamos cuánto tiempo tarda el programa en ejecutar este fragmento de código. Para ello utilizaremos
los métodos console.time y console.timeEnd.
let part = 0;
console.time('Leibniz');
for (let k = 0; k < 10000000; k++) {
part = part + ((-1) ** k) / (2 * k + 1);
}
console.timeEnd('Leibniz'); // -> Leibniz: 456.60498046875 ms
let pi = part * 4;
console.log(pi); // -> 3.1415925535897915
Con console.time, indicamos dónde iniciar la medición del tiempo, mientras que
con console.timeEnd finalizamos la medición, y el resultado se muestra en la consola en este punto
(obviamente el resultado que obtengas será diferente al del ejemplo). El tiempo se da en milisegundos.
En las llamadas de los métodos console.time y console.timeEnd, podemos especificar una
cadena (en el ejemplo es 'Leibnitz') que identificará nuestro cronómetro en caso de que usemos
muchos de ellos en nuestro programa.
let part = 0;
for (let k = 0; k < 10000000; k++) {
part = part + ((-1) ** k) / (2 * k + 1);
}
let pi = part * 4;
console.log(pi); // -> 3.1415925535897915
La parte variable contendrá un resultado parcial, que se modificará en cada iteración del bucle for. El
bucle se ejecutará diez millones de veces (es decir, k tomará valores de 0 a 999999). La parte que más
tiempo consumirá será la ejecución del bucle for, porque en cada iteración se realizan operaciones como
sumar, multiplicar, dividir y exponenciación.
Veamos cuánto tiempo tarda el programa en ejecutar este fragmento de código. Para ello utilizaremos
los métodos console.time y console.timeEnd.
let part = 0;
console.time('Leibniz');
for (let k = 0; k < 10000000; k++) {
part = part + ((-1) ** k) / (2 * k + 1);
}
console.timeEnd('Leibniz'); // -> Leibniz: 456.60498046875 ms
let pi = part * 4;
console.log(pi); // -> 3.1415925535897915
Con console.time, indicamos dónde iniciar la medición del tiempo, mientras que
con console.timeEnd finalizamos la medición, y el resultado se muestra en la consola en este punto
(obviamente el resultado que obtengas será diferente al del ejemplo). El tiempo se da en milisegundos.
En las llamadas de los métodos console.time y console.timeEnd, podemos especificar una
cadena (en el ejemplo es 'Leibnitz') que identificará nuestro cronómetro en caso de que usemos
muchos de ellos en nuestro programa.
let part = 0;
console.time('Leibniz');
for(let k = 0; k < 10000000; k++) {
part = part + (k % 2 ? -1 : 1) / (2 * k + 1);
}
console.timeEnd('Leibniz'); // -> Leibniz: 175.5458984375 ms
let pi = part * 4;
console.log(pi);
Como puedes ver, ¡incluso un cambio tan pequeño nos permite más del doble de la velocidad
del programa! El uso de los métodos console.time y console.timeEnd nos permite
analizar el rendimiento de nuestro código. Si tenemos la impresión de que algo funciona
demasiado lento, pero no sabemos qué fragmento de código es el responsable, podemos
realizar mediciones, localizar el problema y, opcionalmente, intentar optimizar el código. Como
decíamos antes, hay muchas herramientas que también nos ayudan en esto. Algunos de ellos
están integrados en las herramientas de desarrollo integradas con el navegador, pero a
menudo los métodos que se muestran son suficientes para realizar pruebas básicas.
Intenta probar ambas soluciones en tu entorno local y analiza qué diferencias en los tiempos
obtienes.
Resumen
La capacidad de utilizar las herramientas de desarrollo, incluido el debugger, es muy
importante en el trabajo de un programador. Permite, entre otros, eliminar eficazmente errores
lógicos en nuestro código, así como optimizarlo.
Sin el uso de la ejecución paso a paso, a menudo no podremos descubrir las razones por las
que nuestro programa no funciona como se esperaba. La posibilidad de detener la ejecución
de una determinada instrucción, comprobando el estado actual de las variables o rastreando
la secuencia de llamadas a funciones anidadas, puede resultar crucial a la hora de probar y
arreglar nuestro programa..
Tareas
Tarea 1
let end = 2;
for(let i=1; i<=end; i++) {
console.log(i);
}
Debería dar como salida los números 1 y 2 en la consola. Usa el debugger para hacer que el
programa genere los números 1, 2, 3, 4 y 5. No modifiques el código del programa. Usa solo
puntos de interrupción y la opción de modificar variables.
Tarea 2
Use el debugger para comprender por qué el resultado final registrado es igual a cero cuando
en cada iteración del for, aumenta un valor de bucle la variable result. Utiliza Watch para
realizar un seguimiento de los cambios en las variables seleccionadas.
let counter = 0;
let maxValue = 10;
let result = 1;
debugger;
for (counter = 0; counter < maxValue; counter++) {
console.log(result);
result *= maxValue - counter - 1;
}
Tarea 3
function max(array) {
let maxValue = array[1];
for(let i=1; i<array.length; i++) {
if(array[i] > maxValue) {
maxValue = array[i];
}
}
return maxValue;
}