Kitabı oku: «JavaScript: Guía completa», sayfa 4
Pasar parámetros a las funciones
Hasta ahora, hemos escrito funciones que elaboran datos que ya poseen y devuelven como mínimo un valor.
En realidad, es posible pasar uno o más datos (denominados parámetros) a la función en el momento en que esta se llama.
La función utilizará los datos pasados para sus elaboraciones y después estos influirán en el resultado de dicha función.
Comprobémoslo con un ejemplo:
<body><p id="output"></p><img id="carita" /><script type="text/javascript">function queDia(nombre) {const dia = new Date().getDay();const nombreDia = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'];return `Hola ${nombre}! Feliz ${nombreDia[dia]}`;}document.getElementById('output').innerHTML = queDia('Alessandra');</script></body>
Puedes encontrar este ejemplo en el archivo FuncionesParametros .html
El comportamiento de esta función debería resultarte familiar; así podemos centrarnos en la gestión del parámetro.
Como puedes ver, la función, desde su definición, está predispuesta a aceptar un parámetro, nombre, en nuestro caso, que posteriormente será pasado (‘Alessandra’) en el momento en que la función es llamada.
En este ejemplo, hemos pasado un único parámetro, pero las funciones de JavaScript pueden aceptar más de uno.
Con este propósito, os proponemos otro ejemplo:
<script type="text/javascript">function cuantosDias(año, mes, dia, NombreEvento) {const hoy = new Date();const milsegEnUnDia = 1000 * 60 * 60 * 24; // los milisegundos de un día// indico a media noche la fecha de hoy, de no ser así la resta entre ambas fechas no nos dará la hora correctahoy.setHours(0, 0, 0, 0);const fechaEvento = new Date(año, mes - 1, dia)const diferenciaHoras = Math.abs(fechaEvento.getTimezoneOffset() / 60) - Math.abs(hoy.getTimezoneOffset() / 60);hoy.setHours(hoy.getHours() - diferenciaHoras);if (fechaEvento > hoy) {return `Faltan ${(fechaEvento.getTime() - hoy.getTime()) / milsegEnUnDia} días para ${NombreEvento}`;} else {return 'La fecha ya pasó'}}document.getElementById('output').innerHTML = cuantosDias(2017, 12, 25, 'Navidad');</script>
Puedes encontrar este ejemplo en el archivo FuncionesParametros1 .html
Esta vez la función es más compleja y prevé que se pasen cuatro parámetros: un año, un mes, un día y un evento. La función utilizará los primeros tres parámetros para crear una fecha y calcular cuántos días faltan para dicha fecha (obviamente, si esta todavía no ha pasado). El cuarto parámetro no es otro que un nombre para el evento representado por la fecha a partir de la cual calculamos los días que faltan.
Veamos cómo ocurre todo esto, puesto que la función contiene muchas novedades.
En primer lugar, además de la constante que representa la fecha, se crea otra que representa la duración de un día en milisegundos, porque la diferencia entre las fechas será devuelta exactamente en milisegundos y nosotros podremos utilizar el valor de esta constante para aplicar el resultado obtenido a varios días.
const milsegEnUnDia = 1000 * 60 * 60 * 24
Una vez hecho esto, indicamos la fecha de hoy a media noche. Si no lo hacemos, la fecha contiene también la hora del momento en que se ha creado y será más difícil de comparar con la fecha que crearemos con los parámetros pasados a la función que, en cambio, no contendrá un horario y, por tanto, hará referencia a la media noche:
hoy.setHours(0, 0, 0, 0);
setHours
, efectivamente, ajusta la hora de una fecha. Requiere como argumentos hora, minutos, segundos y milisegundos.
En este momento, creamos la fecha con los parámetros que han sido pasados, sin olvidarnos de restar 1 al valor pasado para el mes, dado que JavaScript utiliza para los meses números que van del 0 (enero) al 11 (diciembre).
const fechaEvento = new Date(año, mes - 1, dia);
Para que nuestra función se comporte correctamente con o sin la horal legal, debemos ajustar el horario de la fecha de hoy de manera que tenga en cuenta el posible cambio de hora causado por la hora legal:
const diferenciaHoras = Math.abs(fechaEvento.getTimezoneOffset() / 60) - Math.abs(hoy.getTimezoneOffset() / 60);hoy.setHours(hoy.getHours() - diferenciaHoras);
Como ya sabéis (puesto que lo hemos visto en el capítulo ¡Hola mundo!), getTimezoneOffset
devuelve la diferencia en minutos entre el UTC y la hora local. Si dividimos este resultado entre 60, obtendremos las horas.
En este momento, recurrimos al objeto Math
, que todavía no había salido, y que nos permite ejecutar operaciones matemáticas. En concreto con
abs()
obtenemos la parte absoluta (positiva) de un número (en España, getTimezoneOffset
devuelve siempre un número negativo).
Si restamos el resultado de la diferencia en horas entre los dos eventos, obtenemos el espacio que necesitamos para alinear la fecha actual con la fecha de nuestro evento: lo hacemos con setHours()
.
NOTA | Existen diversas funciones que nos permiten elaborar un número con el objeto Math:•abs() : devuelve el valor absoluto de un número.•acos() : devuelve el arcocoseno en radianes.•asin() : devuelve el arcoseno en radianes.•atan() : devuelve el arcotangente como valor numérico comprendido entre ¬Π/2 y Π/2 radianes.•atan2() : devuelve el arcotangente del cociente de sus argumentos.•ceil() : devuelve el valor de su argumento redondeando por exceso al entero más cercano.•cos() : devuelve el coseno expresado en radianes.•exp() : devuelve el valor de e elevado al valor pasado como argumento.•floor() : devuelve el valor de su argumento redondeado por defecto al entero más cercano.•log() : devuelve el logaritmo natural en base e del número pasado como argumento.•max() : devuelve el valor máximo entre los argumentos que le han pasado.•min() : devuelve el valor mínimo entre los argumentos que le han pasado.•pow() : devuelve el valor del primer argumento pasado, elevado al valor del segundo argumento.•random() : devuelve un número aleatorio comprendido entre 0 y 1.•round() : devuelve su argumento redondeado al entero más cercano.•sin() : devuelve el seno expresado en radianes.•sqrt() : devuelve la raíz cuadrada.•tan() : devuelve la tangente. |
Cuando las fechas se indican en la misma referencia horaria, con una instrucción if
configuramos los mensajes que la función debe devolver: si el evento es posterior a hoy, es decir, su fecha es mayor que la fecha actual, el mensaje contendrá el número de días que faltan para el event; si no, indicará que la fecha ya ha pasado.
if (fechaEvento > hoy) {return `Faltan ${(fechaEvento.getTime() - hoy.getTime()) / milsegEnUnDia} días a ${NombreEvento}`;} else {return 'La fecha ya pasó'}
En nuestro ejemplo, hemos pasado a la función los valores (año-mes-día) para construir una fecha, pero nada nos impide pasar a la función directamente un objeto fecha:
<script type="text/javascript">function cuantosDias(fecha, evento) {let hoy = new Date();const milsegEnUnDia = 1000 * 60 * 60 * 24hoy.setHours(0, 0, 0, 0);const diferenciaHoras = Math.abs(fecha.getTimezoneOffset() / 60) - Math.abs(hoy.getTimezoneOffset() / 60);hoy.setHours(hoy.getHours() - diferenciaHoras);if (fechaEvento > hoy)return `Faltan ${(fecha.getTime() - hoy.getTime()) / milsegEnUnDia} días para ${evento}`;elsereturn 'La fecha ya pasó'}const fechaEvento =new Date(2017, 11, 25);document.getElementById('output').innerHTML = cuantosDias(fechaEvento, 'Navidad');</script>
Puedes encontrar este ejemplo en el archivo FuncionesParametros2 .html
Funciones anónimas
En los ejemplos con los que hemos trabajado hasta ahora, siempre hemos asignado un nombre a las funciones. JavaScript también permite crear funciones anónimas y asignarlas a una variable. Esta operación es posible porque en JavaScript function
es un tipo que se puede almacenar en una variable. En otros lenguajes esto no es posible.
<script type="text/javascript">let suma = function (x,y){return x + y;}console.log(suma(34,67));</script>
Puedes encontrar este ejemplo en el archivo FuncionesAnonimas .html
Al utilizar la variable, podemos especificar entre sus paréntesis los parámetros que deseamos utilizar.
Las funciones anónimas también pueden ser utilizadas como parámetros para otras funciones. A lo largo de este libro, conoceremos ejemplos de este uso.
El área de validez de variables y constantes
Cuando se empiezan a utilizar funciones, es preciso prestar atención al área de validez de variables y constantes, es decir, a las partes del código donde variables y constantes pueden ser utilizadas. En nuestro ejemplo, hemos declarado la constante dia
dentro de la función EsFinDeSemana
.
function EsFinDeSemana() { const dia = new Date().getDay(); return dia === 0 || dia === 6;}
La constante existe solo dentro de la función y no puede ser utilizada fuera de ella. Por esta razón, podemos decir que el área de validez (en inglés scope) de la constante dia
es la función esFinDeSemana
. También se dice que dia
es una constante local de la función esFinDeSemana
. El mismo discurso vale para las variables.
Si definimos una variable o una constante fuera de una función, esta se denomina global y puede ser utilizada en cualquier lugar, incluso en posibles funciones, y mantiene su valor durante toda la ejecución del código.
Lo que acabamos de explicar es la teoría general referente al área de validez de las variables.
ECMAScript 6 presenta una aclaración más: el área de validez a nivel de bloque (blocked-level).
Tanto let
como const
generan variables y constantes a nivel de bloque.
Esto significa que, si en una función, por ejemplo, hay un bloque if
y una variable o una constante se definen con let
o const
en el bloque if
, estas existirán solo en el bloque y no en toda la función.
Por ejemplo:
function miFuncion() {if (true) {let variable = 123;}console.log(variable);}
La función anterior nos devolvería un error porque variable
existe solo en el bloque if
y no la podemos utilizar fuera de ella para mostrar el valor en la consola.
Debemos profundizar en esta cuestión, precisando que let
fue introducida a partir de ECMAScript 6 (2015) y no está disponible en las versiones anteriores del programa.
En las versiones de JavaScript anteriores, para declarar una variable se utilizaba la palabra var,
que también se utiliza en ECMAScript 6.
Tratemos de entender por qué es conveniente utilizar let
.
Las instrucciones:
var nombreUsuario1 = 'Mario Rossi';let nombreUsuario2 = 'Paolo Bianchi';
desarrollan exactamente las mismas operaciones:
•Crean un área de memoria suficientemente amplia para conservar los caracteres de las cadenas.
•Asignan a esta área el nombre “nombreUsuario1”/“nombreUsuario2”.
La diferencia entre ambas consiste en su área de validez o scope.
Ya hemos dicho que el área de validez de let
es el bloque. Pues bien, el área de validez de var
, en cambio, es la función.
Veamos un ejemplo para explicarlo mejor; utilizamos una función que acepta como parámetros de entrada un array de números y calcula su suma y su media aritmética.
function mediaConVar1(numeros) {var suma = 0;for (var indice = 0; indice < numeros.length; indice += 1) {suma += numeros[indice];}var media = suma / numeros.length;return `Los ${numeros.length} números tienen suma=${suma} y media=${media}. El índice es ${indice}`;}
Ejemplo de uso de la función: mediaConVar1([84, 36, 61, 67, 22, 20, 22, 16, 79, 54])
;
Puedes encontrar este ejemplo en el archivo Scope .html
En esta función hemos definido tres variables: suma
, indice
y media
. Observa que están definidas en el punto en el que se utilizan: suma
inmediatamente antes del bucle for
del cálculo de la suma, indice
dentro del bucle for
y media
después de que se ha calculado la suma.
La definición de variables en el punto en que se utilizan la primera vez es común a muchos otros lenguajes de programación.
Sin embargo, JavaScript, a diferencia de otros lenguajes, tiene un comportamiento peculiar (el término técnico es hoisting): las instrucciones var
dentro de una función se ejecutan antes que todas las otras, sea donde sea que se encuentren en la función.
Para demostrar este efecto, añadimos a nuestra función una línea que escribe en la consola del navegador el valor de la variable media antes que la línea que la define:
var suma = 0;console.log(`media = ${media}`)for (var indice = 0; indice < numeros.length; indice += 1) {suma += numeros[indice];}var media = suma / numeros.length;
Pensaríamos que se produciría un error durante la ejecución, dado que utilizamos la variable media
antes que su definición.
En lugar de eso, en la consola podemos ver:
media = undefined
(o media = "", según el navegador que se utilice)
Este mensaje significa que la variable media
existe, pero que por el momento no tiene ningún valor. Esto se produce porque, de hecho, JavaScript, en fase de ejecución, reorganiza el código de esta manera:
function mediaConVar1bis(numeros) {var suma;var indice;var media;suma = 0;for (indice = 0; indice < numeros.length; indice += 1) {suma += numeros[indice];}media = suma / numeros.length;return `Los ${numeros.length} números tienen suma=${somma} y media=${media}. El índice es ${indice}`;}
donde todas las variables se definen al inicio de la función, pero no se les asigna un valor de inmediato.
Este comportamiento puede llevar a engaño incluso a los más expertos, puesto que se sugiere siempre definir explícitamente las variables al inicio de la función para que se vea que, efectivamente, es JavaScript quien gestiona el código:
function mediaConVar2(numeros) {var suma = 0, indice, media;for (indice = 0; indice < numeros.length; indice += 1) {suma += numeros[indice];}media = suma / numeros.length;return `Los ${numeros.length} números tienen suma=${suma} y media=${media}. El índice es ${indice}`;}
En el ejemplo, para crear un código más simplificado, hemos agrupado las definiciones en una única instrucción var
(las variables separadas por comas) y hemos inicializado (mediante asignación), donde es necesario, el valor (suma = 0).
De este modo, con todas las variables al inicio, no es posible engañarse y utilizar una variable antes de que sea definida.
La solución de definir todas las variables al inicio, sin embargo, no es demasiado elegante y son pocos los lenguajes de programación que aconsejan o imponen esta práctica.
De hecho, esta puede causar errores en el código, dado que aleja el punto en que se define la variable del punto en que se utiliza y es más difícil mantener bajo control su gestión de forma correcta, sobre todo cuando el código es largo y complejo.
Así, en ECMAScript 6 (2015), para evidenciar este problema, se introdujo la instrucción let,
que obliga a JavaScript a comportarse de una forma más parecida a los otros lenguajes: así se evita el hoisting, las variables se definen en el momento de la instrucción en que están creadas, no existen antes (por lo que no pueden ser utilizadas antes) y, si la definición se produce dentro de un bloque (entre dos {}), la variable no existe fuera de él.
NOTA | Recuerda que hemos dicho que el área de validez de let es el bloque, mientras que la de var es la función. |
Si utilizamos let
, la función propuesta se convierte en:
function mediaConLet(numeros) {let suma = 0;for (let indice = 0; indice < numeros.length; indice += 1) {suma += numeros[indice];}let media = suma / numeros.length;return `Los ${numeros.length} números tiene suma=${suma} y media=${media}. El índice es ${indice}`;}
Puedes encontrar este ejemplo en el archivo Scope2 .html
Si intentamos ejecutar esta función se producirá un error (visible en la consola del navegador) que indica que, en la instrucción de return
, se utiliza una variable, indice
, no definida.
De hecho, la variable indice
, definida con let
, existe solo dentro del bucle for
(su área de validez es el bucle for
).
Por lo tanto, debemos eliminar la lectura de la variable indice
o definirla justo antes del bucle for
(sin embargo, por norma general, las variables utilizadas como índice de un bucle no tienen ninguna utilidad fuera del bucle mismo):
return `Los ${numeros.length} números tienen suma=${suma} y media=${media}`;
Incluso si añadimos la línea:
console.log(`media = ${media}`)
antes del ciclo for
se producirá un error, esta vez determinado por el intento de acceder a la variable media
antes de su definición.
Formularios y eventos
Ha llegado el momento de aprender a leer el input del usuario con formularios adecuados. También veremos cómo ejecutar el código cuando se verifica un determinado evento.
Temas tratados
•Lectura y escritura en un formulario
•Eventos HTML y HTML5
•Respuesta a un evento
•Listener
•Subdivisión de cadenas en varias partes
En los ejemplos con los que hemos trabajado en el capítulo anterior hemos escrito directamente en el código los parámetros que se deben pasar a nuestras funciones, como la fecha respecto a la cual queremos calcular los días que faltan.
En esta ocasión queremos aprender a leer el input del usuario y a utilizarlo para nuestros procesos mediante JavaScript.
Continuaremos con el ejemplo que calcula el número de días que faltan para una fecha determinada, pero pediremos al usuario que nos proporcione dicha fecha.
Sin embargo, antes es necesario ver cómo leer el input del usuario. Volveremos al cálculo de los días al final del capítulo.
Naturalmente, para permitir al usuario que proporcione un input debemos preparar un formulario:
<body><form id='miFormulario'><input type="date" id='fecha'> <button type="button" onclick='leeFormulario()' id="boton">Envía</button></form><p id="output"></p><script>function leeFormulario() {document.getElementById("output").innerHTML = `La fecha seleccionada es: ${document.getElementById('fecha').value}`;}</script></body>
Puedes encontrar este ejemplo en el archivo Formulario .html
Hemos creado un formulario muy sencillo que contiene un campo <input>
HTML5 de tipo fecha y un botón.
Debemos precisar varios aspectos sobre estos objetos.
<input type="date" id='fecha'><button type="button" onclick='leeFormulario()' id="boton">Envía</button>
En primer lugar, debemos decir que, en el momento en que se ha escrito este libro (a finales de 2017), la última versión disponible de Firefox no muestra para el campo de tipo fecha ningún elemento para la selección de la fecha, que por tanto deberá introducirse manualmente.
Por eso, te aconsejo que pruebes este ejemplo con un navegador distinto, como Chrome o Edge.
Además, observa que, en cuanto se refiere al botón, hemos tenido que especificar de forma explícita su tipo (type="button"
) para sobreescribir el comportamiento predeterminado de los botones en los formularios, que es el de submit
, es decir, de envío del formulario.
Solo así, el botón puede comportarse como necesitamos y ejecutar la función Java-Script que está asociada a su evento onclick
.
Podemos definir un evento como algo que ocurre en relación a un elemento de la página HTML, a menudo, aunque no siempre, vinculado a una acción del usuario. Cuando se utiliza en una página HTML, JavaScript es capaz de reaccionar a estos eventos.
En nuestro caso concreto, JavaScript es capaz de reaccionar al evento onclick
sobre el botón con id=”boton”.
Como se puede ver, el evento onclick
se verifica cuando un usuario hace clic sobre el botón.
La Tabla 7.1 resume los principales eventos HTML a los cuales JavaScript puede reaccionar.
NOTA | Volveremos a los eventos ondragstart , ondragstart y ondrop en el capítulo dedicado al arrastre. En él, presentaremos también otros eventos que se utilizan para arrastrar objetos. |
Tabla 7.1 – Eventos.
Evento | Descripción |
onchange | Se verifica cuando un elemento HTML se modifica |
onclick | Se verifica cuando el usuario hace clic sobre un elemento HTML |
onmouseover | Se verifica cuando el usuario pasa el ratón por encima de un elemento HTML |
onmouseout | Se verifica cuando el usuario mueve el ratón fuera de un elemento HTML |
onkeydown | Se verifica cuando el usuario pulsa una tecla del teclado |
onload | Se verifica cuando el navegador ha completado la carga de la página |
ondragstart | Se verifica cuando el usuario empieza a arrastrar un elemento HTML5 |
ondragover | Se verifica cuando el usuario arrastra un elemento HTML5 sobre otro |
ondrop | Se verifica cuando el usuario libera un elemento HTML5 tras haberlo arrastrado |
Después de esta panorámica sobre los eventos, volvemos a nuestro código y observamos que, en este caso, pedimos a JavaScript que ejecute una función cuando se verifica el evento.
La función que se ejecuta es leeFormulario
y se limita a leer el valor del campo <input id=fecha>
y a escribirlo en la página:
document.getElementById("output").innerHTML = `La fecha seleccionada es: ${document.getElementById('fecha').value}`;
Para leer el dato contenido en el formulario, accedemos a su valor tras haberlo identificado con getElementById
.
El valor del campo es una cadena de texto (Figura 7.1) en el formato aaaa-mm-dd. Debemos tenerlo en cuenta en nuestras futuras creaciones.
Figura 7.1 – El valor del campo fecha.
Del mismo modo que leemos el contenido del campo, también podemos escribirlo. Por ejemplo, queremos que, cuando se cargue la página, antes de la selección por parte del usuario, el campo muestre, como valor predeterminado, la fecha actual del sistema.
Corrige el código JavaScript del siguiente modo:
<script>const hoy = new Date();function leeFormulario() {document.getElementById("output").innerHTML = `La fecha seleccionada es: ${document.getElementById('fecha').value}`;}const año = hoy.getFullYear();const mes = hoy.getMonth() + 1;const dia = hoy.getDate();const fechaActual = `${año}-${mes}-${dia}`;document.getElementById("fecha").value = fechaActual;</script>
Puedes encontrar este ejemplo en el archivo Formulario1 .html
Fuera de la función que reacciona al evento clic sobre el botón, creamos un objeto fecha que contiene la fecha actual. Leemos y almacenamos el año, el mes y el día en constantes especiales y después utilizamos estas constantes para construir una cadena que representa la fecha en el formato que el campo de entrada se espera (aaaa-mm-dd).
const fechaActual = `${año}-${mes}-${dia}`;
Una vez hecho esto, asignamos al valor del campo fecha la cadena que acabamos de construir:
document.getElementById("fecha").value = fechaActual;
Ahora que ya sabemos leer y escribir en el campo de un formulario, podemos enriquecer nuestro ejemplo con el código JavaScript que calcula la distancia en días entre la fecha seleccionada y la fecha actual.
<form><input type="date" id='fecha'><button type="button" onclick='cuantosDias()' id="boton">Calcula cuántos días faltan para la fecha seleccionada</button></form><p id="output"></p><script>let hoy = new Date();function cuantosDias() {let fechaEvento = new Date(document.getElementById('fecha').value);console.log(fechaEvento);fechaEvento.setHours(0, 0, 0, 0);document.getElementById("output").innerHTML = fechaEvento;const milSegEnUnDia = 1000 * 60 * 60 * 24; // los milisegundos en un díahoy.setHours(0, 0, 0, 0);const diferenciaHoras = Math.abs(fechaEvento.getTimezoneOffset() / 60) - Math.abs(hoy.getTimezoneOffset() / 60);hoy.setHours(hoy.getHours() - diferenciaHoras);if (fechaEvento > hoy) {document.getElementById("output").innerHTML = `Faltan ${(fechaEvento.getTime() - hoy.getTime()) / milSegEnUnDia} días`;} else {document.getElementById("output").innerHTML = 'La fecha ya ha pasado'}}let año = hoy.getFullYear();let mes= hoy.getMonth() + 1;let dia = hoy.getDate();let fechaActual = `${año}-${mes}-${dia}`;document.getElementById('fecha').value = fechaActual;</script>
Puedes encontrar este ejemplo en el archivo Formulario2 .html
Gran parte de este código te debería ser familiar, porque no es otra cosa que una adaptación de lo que hemos visto en el capítulo anterior.
Existen solo algunas apreciaciones que hacer.
A diferencia de lo que hemos hecho con anterioridad, esta vez creamos la fecha no pasando números que representan año, mes y día, sino una cadena extraída de nuestro campo <input type="date">
.
let fechaEvento = new Date(document.getElementById('fecha').value);
Figura 7.2 – La fecha ha sido creada a la una de la madrugada.
La fecha creada con una cadena de texto en el formato “aaaa-mm-dd”, que es el que nos devuelve el campo del formulario, es tratada como UTC y no como local.
En la práctica, para nosotros (que estamos +1 respecto al UTC), la fecha tiene como hora la 1 de la madrugada y no media noche, como cuando creamos la fecha partiendo de los números que representan año, mes y día.
Puedes comprobarlo fácilmente leyendo el resultado que se muestra en la consola (Figura 7.2).
Para solucionar este problema, bastará con indicar la fecha a medianoche como ya hemos hecho en alguna ocasión.