Continuando con el repaso de la Programación estructurada, en este capítulo se hace un repaso de las funciones (los módulos) y dos de las estructuras de control: la secuencia y la selección.
La forma en que se implementa el fundamento de la modularidad, en la PE, es mediante las funciones.
En JavaScript las funciones pueden ser creadas en al menos 3 formas: funciones estándar, funciones anónimas y funciones flecha. Las funciones pueden ser creadas también a partir de su clase padre: la clase Function (una forma rara vez empleada en la práctica).
Por otra parte, las funciones anónimas y las funciones flecha, pueden ser invocadas inmediatamente, lo que se conoce como una Función Expresion Inmediatamente Invocada: una IIFE (Immediately Invoked Function Expression).
Las funciones estándar tienen esencialmente la misma estructura que las funciones en C/C++, excepto que, en lugar del tipo de dato devuelto, se escribe la palabra reservada function:
El nombre_de_la función, al ser un identificador, sigue las mismas reglas que se aplican a los nombres de las variables.
Las instrucciones JavaScript son instrucciones válidas del lenguaje escritas en una secuencia: Una instrucción a continuación de otra y separadas con puntos y comas: ;). Una función puede tener entre 1 y cientos de instrucciones.
La instrucción de retorno: return, termina la ejecución de la función y devuelve el valor o resultado de la expresión. Esta instrucción es opcional y puede estar ubicada en cualquier parte de la función, aunque usualmente es la última. Si en la función no se escribe la instrucción return, la función termina cuando se ejecuta su última instrucción y como no devuelve ningún valor, el resultado es: undefined).
La lista_de_parámetros son los nombres de las variables (separadas con comas) donde la función recibe los datos que necesita. Los parámetros, al igual que las variables declaradas en la función, son temporales (son destruidas cuando la función termina).
Por ejemplo, la siguiente instrucción crea la función estándar "sum2", que recibe dos valores en las variables "x" y "y" y devuelve su suma:
Para llamar (emplear) esta función (desde cualquier parte, inclusive desde dentro de otra función) simplemente se escribe su nombre (sum2) y entre paréntesis, los datos (en este caso los números a sumar). Por ejemplo, para sumar: 7 y 12; 5.76 y 9.32; 54.3123 y 89.2443, se escribe:
Como se dijo, en JavaScript las funciones pueden ser creadas como funciones anónimas:
Donde nombre_de_la_función es la variable a la que se asigna la función anónima. La función (la expresión después del signo igual) es anónima, porque como tal, no tiene nombre. En la práctica, casi siempre la variable a la que se asigna la función se declara con const, pero en la calculadora Javascript, para poder modificarla en caso de error, no se emplea ni let, ni const.
El cuerpo de la función es el mismo que el de una función estándar, por lo que sólo cambia la forma de declararla, así la función suma2, en forma de una función anónima, es:
Y se llama y emplea igual que una función estándar, así para sumar 10 y 15, se escribe:
Las funciones pueden ser creadas también como funciones flecha, de acuerdo a la siguiente sintaxis:
Que es similar a las funciones anónimas. Esta es la forma más práctica cuando la función consta de una sola expresión, pues en esos casos no se requieren ni las llaves, ni la instrucción de retorno (return). Así, la función "sum2 se crea con:
Y, por supuesto, se emplea igual que los otros tipos de funciones. Por ejemplo, para calcular la suma de 12 y 23, se escribe:
Si la función sólo requiere un dato, no es necesario encerrar el parámetro entre paréntesis. Por ejemplo, para calcular el valor de la siguiente expresión:
| \[ \sqrt[3] {{y+ 4! + y^{3.2}} \over {\text{e}^y + \cos(y)} } \] |
Se puede programar la siguiente función flecha:
Con la cual se puede calcular el valor de la expresión para y=3.1:
En ocasiones se requiere programar una función que sólo es utilizada una vez. En ese caso es más eficiente programar una IIFE (una "ifi"):
Las IIFE pueden ser creadas como funciones anónimas o como funciones flecha:
Por ejemplo, si se quiere calcular la suma de los cubos de 1, 5 y 9, se puede escribir la siguiente IIFE:
O también:
La función se encierra entre paréntesis para que JavaScript la trate como una expresión (función expresión) y se le mandan los valores para que sea inmediatamente invocada (inmediatamente ejecutada).
Cuando la expresión o problema a resolver requiere más de una instrucción, que es el caso más frecuente, las instrucciones deben separarse con puntos y comas y se ejecutan una acontinuación de otra, lo que constituye una una secuencia (la estructura de control base de la PE).
Por ejemplo para calcular el área de un triángulo, de lados "a", "b" y "c", con la siguiente expresión:
| \[ \begin{aligned} \text{Area} &= \sqrt{s(s-a)(s-b)(s-c)}\\[0.3cm] donde : &\phantom{=}\\ s &= \dfrac{a+b+c}{2} \end{aligned} \] |
Primero se debe calcular "s" y luego el área (porque sin "s" no es posible calcular el área) por lo tanto ese es el orden obligatorio de la secuencia. Si se invierte ese orden JavaScript genera un error, porque al intentar calcular el valor del área encuentra que la variable "s" aún no existe (haga la prueba):
Probando la función con algunos valores de prueba, se obtiene:
Los parámetros empleados hasta el momento son parámetros por valor. Se denominan así porque sólo reciben valores. Si a la función se manda una variable, el parámetro recibe sólo el valor de la variable, no la variable en si. De esa manera se evita modificar la variable externa (algo que suele dar lugar a errores difíciles de ubicar).
Por ejemplo, si se llama a la función sum2, guardando previamente los valores a sumar en las variables "a" y "b":
La función sum2, no guarda las variables "a" y "b" en las variables "x" y "y", sino guarda únicamente los valores de esas variables, es como si se hubiera mandado a la función los números 7 y 8.
Las variables originales permanecen inalteradas, tal como se puede comprobar en la siguiente función, que recibe dos números en los parámetros "c" y "d", los parámetros son modificados (e imprimidos) dentro de la función, pero, como se puede ver, esa modificación no altera los valores de las variables externas:
Es importante comprender que, aunque los parámetros tengan los mismos nombres que las variables externas, en realidad son otras variables (que no tienen ninguna relación con ninguna variable externa). Son creadas en la función, sólo existen dentro de la función y son destruidas cuando la función termina de ejecutarse.
Al igual que en C/C++, las fuciones pueden tener parámetros por defecto. Estos parámetros se crean asignándoles un valor y es el valor que tienen cuando no reciben otro valor al llamar a la función.
Por ejemplo, en la siguiente función, que calcula la suma de hasta 3 números, los 3 parámetros son parámetros por defecto, con valores iguales a 0:
Si no se manda uno o más de los 3 valores, los parámetros que no reciben un valor toman el valor 0. Ese es un valor conveniente en este caso en particular, porque si sólo se mandan dos valores, la función puede sumar esos valores y como el tercero es 0, no afecta al resultado.
Así, en las siguientes instrucciones, primero se llama a la función con 3 valores, entonces "a" es igual a 1, "b" es igual a 2 y "c" es igual a 3; luego se llama con 2 valores, por lo tanto "a" es igual a 1, "b" igual a 2 y "c" igual a 0; en la tercera llamada se manda un valor, por lo que "a" es igual a 1, mientras que "b" y "c" son iguales a 0 y en la última llamada no se mandan valores, por lo que "a", "b" y "c" son iguales a 0:
Como se puede ver, en todos los casos la función devuelve el resultado correcto y cuando no se manda ningún valor, devuelve 0.
Usualmente los parámetros por defecto se declaran al final de la lista de parámetros, sin embargo, en JavaScript (a diferencia de C/C++) pueden ser declarados en cualquier posición. En esos casos, sin embargo, para que la función emplee el parámetro por defecto se le debe mandar el valor undefined en la posición correspondiente a ese parámetro.
Se debe proceder de la misma manera (mandar undefined) cuando, a pesar de haber declarado los parámetros por defecto al final, se quiere que uno o más de ellos (que no son los último) empleen su valor por defecto. Así, si en la función suma se quiere que el segundo parámetro emplee su valor por defecto, se escribe:
En este caso "a" toma el valor 12, "b" toma el valor 0 (el valor por defecto) y "c" toma el valor 45.
Aunque estrictamente hablando, las funciones en JavaScript (al igual que en C/C++) no admiten parámetros nombrados, pueden ser emulados empleando objetos literales, de acuerdo a la siguiente sintaxis:
Donde, par1, par2, etc., son los nombres de los parámetros. Como se puede ver, los parámetros nombrados también pueden tener valores por defecto. Cuando una función tiene parámetros nombrados, a esos parámetros se debe mandar no solo el valor, sino también el nombre, sin embargo, pueden ser mandados en cualquier orden, a diferencia de los parámetros por valor y por defecto, donde los valores deben ser enviados exactamente en el orden en que fueron declarados al crear la función.
El símbolo de igualdad y las llaves (={}), son necesarios únicamente para los casos en los que se llama a la función sin mandar ningún valor. Si, al crear una función, se sabe con certeza que siempre será llamada con algún valor, entonces puede ser programada sin escribir el símbolo de igualdad y las llaves.
Los parámetros nombrados son particularmente útiles cuando la función consta de 3 o más parámetros, porque, en esos casos, es más fácil recordar un nombre, que la posición en fue declarado un parámetro.
Por ejemplo, para calcular con la siguiente ecuación, la distancia (d) que alcanza un proyectil disparado con una velocidad inicial \(v_0\) (en m/s) y con un ángulo de disparo θ (en grados), siendo la aceleración de la gravedad g:
| \[ d = \dfrac{v^2_0 \sin(2\theta)}{2g} \] |
Se pueden emplear parámetros nombrados. En este caso, es conveniente declarar como parámetro nombrado, con un valor por defecto, la aceleración de la gravedad (g): 9.80665 (m/s2), porque ese valor, que corresponde a la gravedad estándar en la tierra, generalmente no cambia, por lo que rara vez se manda un valor diferente:
Al igual que en C/C++, el texto escrito después de los dos quebrados (//) son comentarios. Como se sabe, los comentarios son ignorados por el lenguaje. Se emplean para documentar el código (hacer más entendible el código). Es aconsejable acostumbrarse a escribir comentarios, porque así es mucho más fácil recordar el razonamiento seguido al elaborar el programa.
Llamando a la función, para calcular la distancia que alcanza un proyectil disparado con una velocidad inicial de 150 m/s y un ángulo de 30 grados, se obtiene:
Por lo tanto, el proyectil alcanza una distancia de 993.5 m.
El programa también puede ser elaborado, empleando la función toRad, para convertir el ángulo a radianes, en lugar de hacer la conversión explícita:
Qué, por supuesto, devuelve el mismo resultado:
En este ejemplo se han mandado los datos a la inversa de como fueron declarados (en desorden) algo que es posible cuando se trabaja con parámetros nombrados.
Para obtener un resultado más preciso, se puede mandar también el valor de la aceleración local de la gravedad g=9.77721 m/s2:
Que es casi 3 metros mayor al resultado anterior (note que, en este caso, los datos han sido mandados igualmente en desorden).
La secuencia es la estructura de control base de la Programación Estructurada y simplemente es un bloque de instrucciones que deben ejecutarse una a continuación de otra, en el orden en que fueron escritas.
En JavaScript se crea una secuencia encerrándola entre llaves y separando las instrucciones con puntos y comas:
Por lo tanto, en JavaScript, prácticamente toda función es una secuencia, excepto, por supuesto, las que tienen una sola instrucción.
En la PE, toda estructura de control debe tener un solo inicio y un solo final, de esta manera dos o más estructuras pueden ser organizadas en una secuencia, con la seguridad de que serán ejecutadas una a continuación de otra, sin que ninguna interfiera con otra.
Si bien, en algunos casos, el orden de las intrucciones dentro de una secuencia puede no tener importancia, en la mayoría de los casos ese orden es muy importante y debe ser cuidadosamente razonado. En general, si el cálculo/tarea de una instrucción depende del resultado/tarea realizada por otra instrucción, esa instrucción debe estar ubicada (siempre) antes de la instrucción en la cual es utilizada.
Por ejemplo, para calcular el valor de la siguiente expresión:
| \[ \begin{gathered} f(x) = 14.0 - x - y^2 - z^2 \\ y = \dfrac{3 x^{2.1}-7.0}{5} \\ z = \dfrac{14.3-y^{1.2}}{4} \end{gathered} \] |
Se puede programar una función que reciba el número x y dentro una secuencia, donde primero se calcula el valor de y, porque solo depende de x (el valor recibido) y porque se requiere para calcular el valor de z, entonces se calcula z y finalmente, con los valores de x, y y z se calcula el valor de la expresión:
Empleando una función flecha y llamándola con algunos valores de prueba, se obtiene:
Note que, como ocurre mayormente, todas las variables locales han sido declaradas como constantes, porque una vez que reciben su valor, no requieren ser modificadas.
Antes de pasar al repaso de las estructuras de control selectivas, se hace un repaso de los operadores relacionales, los operadores lógicos y la evaluación en cortocircuito.
Los operadores relacionales son empleados en la escritura de condiciones en programación. Una condición es toda sentencia de la cual se puede determinar su veracidad (true) o falsedad (false).
Los operadores relacionales comparan dos valores (escritos a ambos lados del operador) y devuelven siempre un valor booleano (true o false). JavaScript cuenta con los siguientes operadores relacionales:
| Operador | Descripción |
|---|---|
| == | Igual a |
| === | Igual y del mismo tipo que |
| != | Diferente de |
| !== | Diferente o de otro tipo que |
| > | Mayor a |
| < | Menor a |
| >= | Mayor o igual a |
| <= | Menor o igual a |
Que tienen la misma sintaxis y semántica que en C/C++, sólo que, por defecto, JavaScript realiza automáticamente las conversiones de tipos que sean necesarias, antes de proceder a comparar los valores.
Por ejemplo, se pueden comparar texto con números:
En estas comparaciones, JavaScript, convierte primero el texto en números y luego compara los valores convertidos.
La diferencia entre los operadores == y != y los operadores === y !==, radica en que los últimos no realizan ningun tipo de conversión, lo que es más seguro desde el punto de vista de programación, razón por la cual se recomienda su uso.
Por ejemplo, en las siguientes comparaciones, se obtienen resultados distintos con los dos tipos de operadores:
Los operadores lógicos comparan valores booleanos (o expresiones que pueden ser convertidas o interpretadas como booleanos). Conjuntamente los operadores relacionales, permiten escribir expresiones lógicas complejas.
JavaScript cuenta con los siguientes operadores lógicos:
| Operador | Descripción |
|---|---|
| ! | Negación |
| && | Y lógico |
| || | O lógico |
Estos operadores tienen la misma sintaxis y semántica que en C/C++, así, el operador de negación: !, cambia el valor booleano de verdadero a falso y viceversa. El operador "Y": &&, devuelve verdadero sólo si los dos valores que compara son verdaderos, en cualquier otro caso devuelve falso. El operador "O" lógico: ||, devuelve falso sólo si los dos valores que compara son falsos, en cualquier otro caso devuelve verdadero.
JavaScript interpreta como valores falsos: el cero (0), el texto vacío (""), el valor nulo (null) y el valor undefined e interpreta como verdaderos cualquier texto no vacío y cualquier valor no nulo o indefinido.
Por ejemplo, las siguientes instrucciones devuelven la negación de algunos de estos valores:
En JavaScript, al igual que en la mayoría de los lenguajes de programación, los operadores lógicos || e && se evalúan en cortocircuito, esto es, si con el primer valor se puede determinar, sin lugar a dudas, el resultado de la expresión, entonces el resultado es devuelto directamente, sin evaluar (sin tomar en cuenta) el valor restante.
En el caso del operador &&, se sabe que el resultado es falso si el primer valor es falso (porque falso && cualquier otro valor es siempre falso). En el caso del operador ||, se sabe que el resultado es verdadero si el primer valor es verdadero (porque verdadero || cualquier otro valor es siempre verdadero). Tal como se puede comprobar en los siguientes ejemplos, donde, aunque los segundos valores son erróneos (son variables y/o funciones inexistentes) no generan ningún error, porque no son evaluados:
A diferencia de C/C++, en JavaScript, el resultado de un operador lógico no siempre es un valor booleano (true/false) sino el valor con el cual se determina la veracidad o falsedad de la expresión.
Así, el operador "Y" (&&) devuelve el primer término, si el mismo equivale a falso y el segundo en caso contrario, mientras que el operador "O" (||) devuelve el primer término si es equivalente a verdadero y el segundo en caso contrario. Tal como se puede ver en los siguientes ejemplos:
Y las siguientes devuelven el segundo valor, pues con el primero no es posible determinar la veracidad o falsedad de la expresión: