1. JavaScript. El lenguaje

Antes de comenzar con JavaScript, es necesario fijar una serie de conocimientos previos. A lo largo del módulo se van a utilizar páginas HTML, por lo que deberemos saber las etiquetas básicas, la estructura de una página web, uso de listas y capas. Junto a HTML, definiremos su estilo mediante CSS, por lo que es necesario conocer su uso básico, y cómo se aplica un estilo a una etiqueta, una clase o un identificador.

1.1. De JavaScript a ECMAScript

JavaScript es un lenguaje de script de amplio uso en la web, y con las nuevas tendencias de aplicaciones de cliente se está convirtiendo en el lenguaje de programación. En los últimos años el lenguaje se ha utilizado en otros contextos como el framework Node.js que permite escribir código de servidor.

Dentro del mundo de la programación, JavaScript tiene mala fama. Gran parte se debe a que se trata de un lenguaje débilmente tipado, que permite usar variables sin declarar y al tratarse de un lenguaje interpretado, no hay compilador que te diga que hay algo erróneo en tu programa. Realmente JavaScript ofrece mucha flexibilidad y las malas críticas vienen más por el desconocimiento del lenguaje que por defectos del mismo.

Contrario a lo que el nombre sugiere, JavaScript tiene poco que ver con Java. La similitud fue una decisión de marketing, allá por el año 1995 cuando Netscape introdujo el lenguaje en el navegador.

Inicios de JavaScript
Figure 1. Inicios de JavaScript

Relacionado con JavaScript tenemos ECMAScript (ES). Cuando el resto de navegadores empezaron a dar soporte a JavaScript, o lenguajes semejantes como JScript, se escribió un documento a modo de especificación de cómo un sistema JavaScript debería funcionar. El lenguaje descrito en este documento es ECMAScript, y lo que especifica es el lenguaje como lenguaje de propósito general, obviando su integración de éste con la web, es decir, nada de DOM ni BOM.

Existen varias versiones de JavaScript. La primera versión ampliamente soportada por diferentes navegadores es ECMAScript 3 (ES3) definida en el año 1999. La especificación 4 promovió varias iniciativas para evolucionar el lenguaje, pero, al menos en lo que implica a la programación web, estas extensiones sólo son útiles cuando son ampliamente soportadas por los navegadores, por lo que se deshechó y se pasó a la versión ECMAScript 5 (ES5), la cual se publicó en 2009. La versión 5 añade al lenguaje algunos objetos, métodos y propiedades, pero el elemento más importante es el modo estricto.

A grosso modo, los navegadores importantes ya soportan ECMAScript 5, e incluso algunos soportan algunas características especificas de ECMASCript 6 (también conocido como ECMAScript 2015) o incluso ES7: http://en.wikipedia.org/wiki/ECMAScript#Implementations

1.1.1. Strict mode

El modo estricto elimina características del lenguaje, lo que simplifica los programas y reduce la cantidad de errores que pueden contener. Por ejemplo, ES5 desaconseja el uso de la instrucción with, lo que provoca que se lance un error al encontrar dicha instrucción, aunque si el navegador no soporta ES5 funcionará correctamente. Otros ejemplos de errores con ES5 es usar variables que no hemos declarado previamente, declarar funciones donde algún parametro está repetido, un objeto donde dos propiedades tengan el mismo nombre, etc…​

Para activar el modo estricto hay que introducir la cadena "use strict", lo que las implementaciones antiguas del lenguaje simplemente pasarán por alto, con lo que este modo es retrocompatible con los navegadores que no lo soporten.

Dependiendo del alcance, ya sea a nivel de función o global usaremos la cadena "use strict" al inicio del fichero o en la primera línea de la función. Por ejemplo, si sólo queremos activar el modo dentro de una función haremos:

function modoEstricto() {
	"use strict";
	// resto de la función
}

Esto significa que el código interno de la función se ejecutará con el subconjunto estricto del lenguaje, mientras que otras funciones puede que hagan uso del conjunto completo.

El objetivo a medio plazo del lenguaje es que en el futuro sólo se soportará el modo estricto, con lo que ES5 es una versión transicional en la que se anima (pero no obliga) a escribir código en modo estricto.

1.2. Uso en el navegador

Para utilizar JavaScript dentro de una página web, usaremos la etiqueta <script>. Mediante esta etiqueta, podemos incluir el código:

  • en el propio documento HTML:

    <script>
    	// Instrucciones JavaScript
    </script>
  • en un archivo externo (con extensión .js), de modo que se pueda reutilizar entre varios documentos:

    <script src="ficheroJavaScript.js"></script>
    async y defer

    Desde HTML5, la etiqueta script admite los siguiente atributos que provocan que el script comience su descarga inmediatamente sin pausar el parser:

    • async: ejecuta el script de manera asíncrona una vez descargado y antes del evento load del objeto window. Puede suceder que los scripts async no se ejecuten en el orden en el que aparecen en la página

    • defer: garantiza que los scripts se ejecutarán en el orden en el que se encuentran en la página. Esta ejecución comienza una vez se ha parseado completamente el documento, pero antes del evento DOMContentLoaded.

    <script src="ficheroJavaScriptAsync.js" async></script>

    Ambos atributos soportan un segundo atributo onload para indicar un manejador que se ejecutará una vez se haya cargado el script.

    Si no indicamos ninguno de estos atributos, el script se parsea y ejecuta inmediatamente de manera síncrona.

  • como parte de un manejador de eventos, como puede ser onclick o onmouseover

    <button onclick="nombreFuncionJavaScript()" />
  • como parte de una URL mediante el pseudo-protocolo javascript:

    <a href="javascript:nombreFuncionJavaScript()">Validar</a>

Normalmente, las referencias JS se incluyen mediante la etiqueta <script> en la cabecera del documento HTML. Pero si queremos que el código JS se ejecute al cargar la página, es conveniente ponerlo antes de cerrar la etiqueta </body> para asegurarnos que se ha cargado todo el DOM.

Una de las consideraciones más importantes es que las páginas web que escribamos deben funcionar incluso si el navegador no soporta JavaScript.

1.2.1. Hola ExpertoJavaUA

Por lo tanto, sabiendo que podemos incluir el código JavaScript dentro del código HTML, ya estamos listos para nuestro saludo:

<!DOCTYPE html>
<html lang="es">
<head>
	<title>Hola ExpertoJavaUA</title>
	<meta charset="utf-8" />
	<script>
		console.log("Hola ExpertoJavaUA desde la consola");
		alert("Hola ExpertoJavaUA desde alert");
	</script>
</head>
<body></body>
</html>

Podéis observar que hemos utilizado la instrucción console.log para mostrar el saludo, pero que realmente, al probarlo en el navegador sólo aparece el mensaje con alert. Para visualizar los mensajes que pasamos por la consola necesitamos utilizar las DevTools que veremos a continuación.

1.3. Herramientas

En el curso vamos a utilizar Intellij IDEA (http://www.jetbrains.com/idea/) para editar nuestros archivos JavaScript, aunque cualquier IDE como Netbeans cumplen de sobra con su propósito.

El segundo elemento imprescindible a la hora de aprender un lenguaje es la referencia del mismo. Para ello, Mozilla mantiene la MDN (Mozilla Developer Network) con una extensa referencia de JavaScript llena de ejemplos, en https://developer.mozilla.org/en/docs/Web/JavaScript/Reference que también está traducida (aunque en muchas ocasiones sin tanto contenido) en https://developer.mozilla.org/es/docs/JavaScript/Referencia. A lo largo de los apuntes tendremos varios enlaces a la MDN para ampliar la información.

Para hacer pequeñas pruebas, el hecho de tener que crear un documento HTML que enlace a un documento JavaScript se puede hacer tedioso. Para ello, existen diferentes "parques" donde jugar con nuestro código. Dos de los más conocidos son JSBin (http://jsbin.com) y JSFiddle (http://jsfiddle.net).

Ambos nos permiten probar código en caliente, e interactuar con código HTML, CSS y usar librerías de terceros como jQuery.

Otro tipo de herramientas que podemos usar son los validadores de código o herramientas de control de calidad del código, que aconsejan una serie de pautas para minimizar los errores. En esta categoría podemos destacar tanto JSHint (http://www.jshint.com) como JSLint (http://www.jslint.com). En ambos casos, existen plugins para todos los IDEs del mercado, así como interfaces web para realizar pruebas básicas con nuestro código.

Finalmente, necesitamos un navegador web el cual incluya un interprete de JavaScript. En el módulo vamos a utilizar Google Chrome y Mozilla Firefox indistintamente. Cada navegador incluye un motor JavaScript diferente, el cual se encarga de interpretar las instrucciones y renderizar la página mediante la implementación de ECMAScript adecuada:

Motor Navegador ECMAScript

Chrome V8

Google Chrome

ECMA-262, edition 5

SpiderMonkey 24

Mozilla Firefox

ECMA-262, edition 5

Nitro

Safari

ECMA-262, edition 5.1

Chakra

Internet Explorer

ECMA-262, edition 5

1.3.1. Dev Tools

Y por último, aunque cada vez menos necesarias como herramientas de terceros, tenemos las herramientas de depuración de código. Los navegadores actuales incluyen las herramientas para el desarrollador que permiten interactuar con la página que se ha cargado permitiendo tanto la edición del código JavaScript como el estilo de la página, visualizar la consola con los mensajes y errores mostrados, evaluar y auditar su rendimiento, así como depurar el código que se ejecuta. Dentro de este apartado tenemos Firebug (http://getfirebug.com) como una extensión de Firefox, y las herramientas que integran los navegadores:

A continuación vamos a centrarnos en las Chrome Developer Tools. Nada más abrirlas, ya sea con el botón derecho e Inspeccionar Elemento, mediante el menú de Ver ▸ Opciones para desarrolladores ▸ Herramientas para Desarrolladores o mediante F12, Ctrl+Shift+I o en Mac Command+Opt+I, aparece activa la pestaña de Elements.

DevTools en Google Chrome
Figure 3. DevTools

Se puede observar que la pestaña Elements divide la pantalla en varios bloques:

  • Página web, en la parte superior, donde se muestra la representación de la misma

  • HTML, a la izquierda, con el código de la página (representación DOM), la cual, conforme pasemos el ratón por el código, se resaltará la presentación visual en el bloque anterior. En cualquier momento podremos editar el contenido de un nodo, añadir o eliminar atributos e incluso trasladar o eliminar nodos enteros.

  • CSS, a la derecha, con los estilos (styles) estáticos y los calculados, así como los eventos del elemento seleccionado en el bloque HTML. Además de poder ver qué reglas están activas, podemos habilitar o deshabilitar propiedades, editar las reglas de las pseudo-clases (active, hover, etc…​) y acceder al fuente de un determinado estilo.

  • Consola, en la parte inferior, donde veremos los mensajes y errores de la página.

Si queremos visualizar que nodo se seleccionaría al utilizar un determinado selector, podemos hacer uso del método inspect(selector) dentro de la consola. Veremos el uso de selectores mediante las DevTools en la tercera sesión donde estudiaremos JavaScript y DOM.

Otra pestaña con la que vamos a trabajar es la de fuentes (Sources), desde la cual podemos editar el código fuente de nuestros archivos JS, CSS o HTML (desde la caché local de Chrome). Las DevTools almacenan un histórico de nuestros cambios (Local Modifications) desde donde podemos revertir un cambio a un punto anterior. Una vez tenemos la versión definitiva, podemos guardar los cambios en el fuente de nuestro proyecto mediante Save As.

Además, si queremos podemos depurar el código de nuestra aplicación haciendo uso de manera conjunta del código fuente y del panel derecho de Sources, donde podemos añadir breakpoints, pausar la ejecución del código al producirse una excepción, así como evaluar el contenido de las variables (pasando el ratón por encima del código) y la pila de ejecución.

Debug en las DevTools
Figure 4. Debug en las DevTools
Un tutorial muy completo sobre las Dev-Tools, en vídeo pero con subtítulos en castellano y patrocinado por Google es http://discover-devtools.codeschool.com/

1.3.2. Console API

En los apuntes vamos a usar el objeto console para mostrar resultados (también podríamos usar alert pero es más molesto). Este objeto no es parte del lenguaje pero sí del entorno y está presente en la mayoría de los navegadores, ya sea en las herramientas del desarrollador o en el inspector web.

Así pues, dentro de la consola de las Dev-Tools podremos visualizar los mensajes que enviemos a la consola así como ejecutar comandos JavaScript sobre el código ejecutado.

Centrándonos en la Console API, los métodos que podemos usar son:

  • log(): muestra por la consola todos los parámetros recibidos, ya sean cadenas u objetos.

    • info(), warn(), error(): muestran mensajes con diferentes niveles de log, normalmente representados con un color distinto.

  • dir(): enumera los objetos recibidos e imprime todas sus propiedades.

  • assert(): permite comprobar si se cumple una aserción booleana.

  • time() y timeEnd(): calcula el tiempo empleado entre las dos instrucciones

Por ejemplo, si ejecutamos el siguiente código:

console.log("prueba", 1, {}, [1,2,3]);
console.dir({uno: 1, dos: {tres: 3}});

Obtendremos:

Uso del objeto console
Figure 5. Uso del objeto console

Si queremos, podemos escribir en la consola sin necesidad de usar console.log(). Esto nos permite probar pequeños cambios o consultar valores de determinados campos, similar a un entorno de depuración que ofrezca un IDE, con completado de código automático (mediante TAB).

Más información del uso de Console API en https://developer.mozilla.org/en-US/docs/Web/API/console

1.4. Datos y variables

Como todo lenguaje de programación, JavaScript soporta tanto datos de texto como numéricos, los cuales se almacenan en variables.

1.4.1. Variables

Para declarar una variable, se utiliza la palabra clave var delante del nombre de variable.

var contador; // undefined
contador = 5;
Los comentarios siguen la misma sintaxis de Java mediante // y /* */

Los nombres de las variables pueden empezar por minúsculas, mayúsculas, subrayado e incluso con $, pero no pueden empezar con números. Además el nombrado es sensible al uso de las mayúsculas y minúsculas, por lo que no es lo mismo contador que Contador.

Se recomienda que las variables comiencen con minúsculas, sigan la notación camelCase, tengan un nombre auto-explicativo, y que se evite el uso de $ ya que se trata de un carácter muy utilizado por las librerías (en especial, por jQuery) y puede hacer el código menos legible.

El tipo de datos asociado a la variable se asigna de manera automática. Si al declarar la variable no le asignamos ningún valor, JavaScript le asigna el tipo undefined. Posteriormente podemos cambiar su tipo en cualquier momento asignándole un nuevo valor.

La diferencia entre undefined y null es académica y no muy significativa. En los programas en los cuales sea necesario comprobar si algo "tiene un valor", podemos usar la expresión algo == undefined, ya que aunque no sean el mismo valor, la expresión null == undefined producirá verdadero.

Otra característica de JavaScript es que podemos asignar valores a variables que no hemos declarado:

var a = 3;
b = 5;
c = a + b; // 8

Al hacer esto, la variable pasa a tener un alcance global, y aunque sea posible, es mejor evitar su uso. Por ello, esta característica queda en desuso si usamos el modo estricto de ES5:

"use strict";
var a = 3;
b = 5; // Uncaught ReferenceError: b is not defined

Un característica especial de JavaScript es la gestión que hace de las variables conocida como hoisting (elevación). Este concepto permite tener múltiples declaraciones con var a lo largo de un bloque, y todas ellas actuarán como si estuviesen declaradas al inicio del mismo:

var a = 3;
console.log(b);	// undefined
var b = 5;

Es decir, al referenciar a la variable b antes de su declaración no obtendremos el valor de la variable global b, sino undefined. Cuando estudiemos las funciones y el alcance de las variables volveremos a este concepto.

Por ello, una buena práctica que se considera como un patrón de diseño (Single Var) es declarar las variables con una única instrucción var en la primera línea de cada bloque (normalmente al inicio de cada función), lo que facilita su lectura:

var a = 3,
	b = 5,
	suma = a + b,
	z;

1.4.2. Texto

JavaScript almacena las cadenas mediante UTF-16. Podemos crear cadenas tanto con comillas dobles como simples. Podemos incluir comillas dobles dentro de una cadena creada con comillas simples, así como comillas simples dentro de una cadena creada con comillas dobles.

"" // cadena vacía
'probando'
"3.14"
'nombre="miFormulario"'
"comemos en el McDonald's"

Para concatenar cadenas se utiliza el operador +. Otra propiedad muy utilizada es length para averiguar el tamaño de una cadena.

var msj = "Hola " + "Mundo";
var tam = msj.length;

JavaScript soporta una serie de operaciones para tratar las cadenas, como charAt(indice) (0-index), substring(inicio [,fin]), indexOf(cadena)/lastIndexOf(cadena), trim(), replace() …​ Por ejemplo:

var nombre = "Bruce Wayne";

console.log(nombre);
console.log(typeof(nombre));  //  "string"
console.log(nombre.toUpperCase());
console.log(nombre.toLowerCase());

console.log(nombre.length); // 11 -> es una propiedad

console.log(nombre.charAt(0));  // "B"
console.log(nombre.charAt(-1));  // ""

console.log(nombre.indexOf("u")); // 2
console.log(nombre.lastIndexOf("ce"));  // 3
console.log(nombre.lastIndexOf("Super"));  // -1

console.log(nombre.substring(6)); // "Wayne"
console.log(nombre.substring(6,9)); // "Way"

console.log(nombre.replace("e","i"));	// "Bruci Wayne";  (1)
1 El método replace sólo sustituye la primera ocurrencia. Ampliaremos su explicación al estudiar las expresiones regulares

Un método que conviene repasar es split(separador) que separa una cadena en un array de subcadenas divididas por el separador. Por ejemplo:

var frase = "En un lugar de la Mancha";
var trozos = frase.split(" "); // ["En", "un", "lugar", "de", "la", "Mancha"]
var truzus = frase.split("u"); // ["En ", "n l", "gar de la Mancha"]

Desde ECMAScript 5, las cadenas se tratan como arrays de sólo lectura, por lo cual podemos acceder a los caracteres individuales mediante la notación de array:

s = "Hola Mundo";
s[0]		// H
s[s.length-1]	// o

1.4.3. Números

Todos los números en JavaScript se almacenan como números de punto flotante de 64 bits. Para crear variables numéricas, le asignaremos el valor a la variable.

var diez = 10; // entero
var pi = 3.14; // real

Si necesitamos redondear una cifra a un número determinado de decimales usaremos el método toFixed(dígitos), el cual devuelve la cifra original con tantos decimales como los indicados por el parámetro dígitos realizando los redondeos necesarios:

var pi = 3.14159265;
console.log(pi.toFixed(0)); // 3
console.log(pi.toFixed(2)); // 3.14
console.log(pi.toFixed(4)); // 3.1416

Podemos utilizar los operadores aritméticos básicos como la suma +, la resta -, el producto * la división / y el resto %. Para cualquier otra operación, podemos utilizar el objeto Math para operaciones como la potencia (Math.pow(base,exp)), raíz cuadrada (Math.sqrt(num)), redondear (Math.round(num), Math.ceil(num), Math.floor(num)), obtener el mayor (Math.max(num1, num2,…​)), obtener el menor (Math.min(num1, num2, …​)), un número aleatorio en 0 y 1 (Math.random()), el número pi (Math.PI), etc…​ Más información en https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math

Para pasar una cadena a un número, utilizaremos la función global parseInt(cadena [, base]), donde la base por defecto es 10, es decir, decimal. Igualmente, para pasar a un número real, usaremos la función global parseFloat(cadena [, base]).

var cadena = "3.14";
var pi = parseFloat(cadena, 10);
var tres = parseInt(pi, 10);

JavaScript emplea el valor NaN (que significa NotANumber) para indicar un valor numérico no definido, por ejemplo, la división 0/0 o al parsear un texto que no coincide con ningún número.

var numero1 = 0;
var numero2 = 0;
console.log(numero1/numero2); // NaN
console.log(parseInt("tres")); // NaN

Si en alguna ocasión queremos averiguar si una variable no es un número, usaremos la función isNaN(valor):

var miNumero = "tres";
if (isNaN(miNumero)) {
	console.log("¡No es un número!");
}

Finalmente, si necesitamos referenciar a un valor númerico infinito y positivo, usaremos la constante Infinity. También existe -Infinity para los infinitos negativos.

var numero1 = 10;
var numero2 = 0;
console.log(numero1/numero2); // Infinity

1.4.4. Booleanos

Podemos guardar valores de true y false en las variables de tipo boolean.

var esBooleano = true;

Los operadores que trabajan con booleanos son la conjunción &&, la disyunción || y la negación !. Además, tenemos el operador ternario (cond) ? valorVerdadero : valorFalso.

Si un valor es 0, -0, null, false, NaN, undefined, o una cadena vacía (""), entonces es falso. Cualquier otro valor en JavaScript a excepción de los valores comentados se convertirán en verdadero al usarlos dentro de un contexto booleano, (por ejemplo, if (true) {};).

Es decir, tanto los números distintos de cero, como los objetos y arrays devuelven verdadero. En cambio, el cero, null y el valor undefined son falsos.

Operador ===

Aparte de los operadores básicos de comparación (<, <=, >, >=, ==, !=), JavaScript ofrece el operador identidad ( o igualdad estricta), representado con tres iguales (===)

La principal diferencia con el operador igualdad (==) es que éste realiza conversión de tipos, mientras que el operador identidad (===) no realiza conversión. Así tendríamos:

var verdadero = ("1" == true);
var falso = ("1" === true);

Por supuesto, también existe su operador negado (!==).

La comunidad JavaScript recomienda usar siempre el operador identidad en contra del igualdad para evitar la conversión de tipos, es decir, mejor 3 iguales que 2.

1.4.5. Coerción de tipos

Cuando a un operador se le aplica un valor con un tipo de datos incorrecto, JavaScript convertirá el valor al tipo que necesita, mediante un conjunto de reglas que puede que no sean las que nosotros esperamos. A este comportamiento se le conoce como coerción de tipos.

console.log(8 * null) // 0
console.log("5" - 1) // 4
console.log("5" + 1) // 51
console.log("five" * 2) // NaN
console.log(false == 0) // true

Por ejemplo, el operador suma siempre intenta concatenar, con lo cual convierte de manera automáticas los números a textos. En cambio, la resta realiza la operación matemática, con lo que parsea el texto a número.

1.4.6. Fechas y horas

Para crear una fecha usaremos el objeto Date, ya sea con el constructor vacío de modo que obtengamos la fecha actual, pasándole un valor que representa el timestamp Epoch (desde el 1/1/70), o pasándole al constructor del día (1-31), mes (0-11) y año.

var fecha = new Date();
console.log(fecha); // Wed May 21 2014 21:03:59 GMT+0200 (Hora de verano romance)

var nochevieja = new Date(2014, 11, 31);
console.log(nochevieja); // Wed Dec 31 2014 00:00:00 GMT+0100 (Hora estándar romance)

Si además queremos indicar la hora, lo podemos hacer mediante tres parámetros más:

var cenaNochevieja = new Date(2014, 11, 31, 22, 30, 0);
console.log(cenaNochevieja); // Wed Dec 31 2014 22:30:00 GMT+0100 (Hora estándar romance)

Una vez tenemos un objeto Date, tenemos muchos métodos para realizar operaciones. Algunos de los métodos más importantes son:

  • getFullYear(): devuelve el año de la fecha con cuatro dígitos

  • getMonth(): número del mes del año (de 0 a 11)

  • getDate(): número de día del mes

Autoevaluación

A partir del siguiente código ¿Qué saldrá por la consola del navegador? [1]

var cenaNochevieja = new Date(2014, 11, 31, 22, 30, 0);
var anyo = cenaNochevieja.getFullYear();
var mes = cenaNochevieja.getMonth();
var diaMes  = cenaNochevieja.getDate();
var incognita = new Date(cenaNochevieja.setDate(diaMes + 1));

console.log(incognita);

Para comparar fechas podemos usar los operadores < o > con el objeto Date, o la comparación de igualdad/identidad con el método getTime() que nos devuelve el timestamp.

var cenaPreNochevieja = new Date(2014, 11, 30, 22, 30, 0);
var cenaNochevieja = new Date(2014, 11, 31, 22, 30, 0);
var cenaNochevieja2 = new Date(2014, 11, 31, 22, 30, 0);

console.log( cenaPreNochevieja < cenaNochevieja ); // true
console.log( cenaNochevieja == cenaNochevieja2 ); // false
console.log( cenaNochevieja === cenaNochevieja2 ); // false
console.log( cenaNochevieja.getTime() == cenaNochevieja2.getTime() ); // true
console.log( cenaNochevieja.getTime() === cenaNochevieja2.getTime() ); // true

Trabajar con fechas siempre es problemático, dado que el propio lenguaje no ofrece métodos para realizar cálculos sobre fechas, o realizar consultas utilizando el lenguaje natural. Una librería muy completa es Datejs (http://www.datejs.com/).

1.4.7. typeof

El operador typeof devuelve una cadena que identifica el tipo del operando. Así pues,

typeof 94.8 // 'number'
typeof "Batman" // 'string'

Los posibles valores de typeof son 'number','string','boolean','undefined','function' y 'object'. El problema viene cuando hacemos esto:

typeof null // 'object'

y en vez de recibir 'null' nos dice que es un objeto. Es decir, si le pasamos un array o null el resultado es 'object', lo cual es incorrecto.

Con lo que para comprobar si es null es mejor usar el operador identidad:

valorNulo === null // true

1.5. Instrucciones

De manera similar a Java, tenemos los siguientes tipos:

  • condicionales: if, if/else (también podemos usar el operador ternario ?:) y switch

  • iterativas: while, do/while, for, for…​in, break y continue

  • tratamiento de excepciones: try/catch y throw

  • depuración: debugger y label

A lo largo del curso usaremos las instrucciones tanto en los ejemplos como en los ejercicios que realizaremos.

Punto y coma

En ocasiones, JavaScript permite omitir el punto y coma al final de una sentencia, pero en otras no. Las reglas que definen cuando se puede omitir el punto y coma son complejas, por lo que se recomienda poner siempre el punto y coma.

El siguiente artículo detalla la casuística de cuando podemos evitar poner el punto y coma: http://www.codecademy.com/blog/78-your-guide-to-semicolons-in-javascript

1.6. Funciones

Las funciones en JavaScript son objetos, y como tales, se pueden usar como cualquier otro valor. Las funciones pueden almacenarse en variables, objetos y arrays. Se pueden pasar como argumentos a funciones, y una función a su vez puede devolver una función (ella misma u otra). Además, como objetos que son, pueden tener métodos.

Pero lo que hace especial a una función respecto a otros tipos de objetos es que las funciones pueden invocarse.

Las funciones se crean con la palabra clave function junto a los parámetros sin tipo rodeados por una pareja de paréntesis. El nombre de la función es opcional.

JavaScript no produce ningún error de ejecución si el número de argumentos y el de parámetros no coincide. Si hay demasiados valores de argumentos, los argumentos de sobra se ignoran. Por contra, si hay menos argumentos que parámetros, los parámetros que han quedado sin asignar tendrán el valor undefined. No se realiza ninguna comprobación de tipos, con lo que podemos pasar cualquier valor como parámetro.

Además, dentro de una función podemos invocar a otra función que definimos en el código a posteriori, con lo que no tenemos ninguna restricción de declarar las funciones antes de usarlas. Pese a no tener restricción, es una buena práctica de código que las funciones que dependen de otras se coloquen tras ellas.

Por último, para devolver cualquier valor dentro de una función usaremos la instrucción return, la cual es opcional. Si una función no hace return, el valor devuelto será undefined.

1.6.1. Función declaración

Si al crear una función le asignamos un nombre se conoce como una función declaración.

function miFuncion(param1, param2) {
	// instrucciones
	return variable;
}

Las variables declaradas dentro de la función no serán visibles desde fuera de la función. Además, los parámetros se pasan por copia, y aquí vienen lo bueno, se pueden pasar funciones como parámetro de una función, y una función puede devolver otra función.

function suma(alfa, beta) {
	return alfa + beta;
}

function calculando(gamma, delta, fn) {
	return fn(gamma, delta);
}

var epsilon = calculando(3, 4, suma);
Autoevaluación

A partir del código anterior, ¿Qué valor obtendríamos en la variables epsilon? [2]

1.6.2. Función expresión

Otra característica de las funciones de JavaScript es que una función se considera un valor. De este modo, podemos declarar una función anónima la cual no tiene nombre, y asignarla a una variable (conocida como función expresión).

var miFuncionExpresion = function (param1, param2) {
	// instrucciones
	return variable;
}

Así pues, el mismo código del ejemplo anterior quedaría así:

var suma = function (alfa, beta) {
	return alfa + beta;
};

var calculando = function (gamma, delta, fn) {
	return fn(gamma, delta);
};

var epsilon = calculando(3, 4, suma);

Y por último, también podemos crear funciones anónimas al llamar a una función, lo que añade a JavaScript una gran flexibilidad:

var calculando = function (gamma, delta, fn) {
	return fn(gamma, delta);
};

var epsilon = calculando(3, 4, function(alfa, beta) {
	return alfa + beta;
});

Como veremos en posteriores sesiones, gran parte de las librerías hacen uso de las funciones anónimas a la hora de invocar a las diferentes funciones que ofrecen.

Las funciones expresión se pueden invocar inmediatamente, lo que hace que sean muy útiles cuando tenemos un bloque que vamos a utilizar una única vez.

Ejemplo de función invocada inmediatamente - IIFE
(function() {
	// instrucciones
})();	// invoca la función inmediatamente

1.6.3. Funciones declaración vs expresión

Cabe destacar una diferencia importante entre estos tipos de funciones, y es que las funciones declaración se cargan antes de cualquier código, con lo que el motor JavaScript permite ejecutar una llamada a esta función incluso si está antes de su declaración (debido al hoisting). En cambio, con las funciones expresión, se cargan conforme se carga el script, y no van a permitir realizar una llamada a la función hasta que sea declarada, por lo que debemos colocarlas antes del resto de código que quiera invocar dicha función.

cantar();
estribillo(); // TypeError: undefined

function cantar() {
	console.log("¿Qué puedo hacer?");
}

var estribillo = function() {
	console.log("He pasado por tu casa 20 veces");
};

1.6.4. Callbacks

Se conoce como callback a una función que se le pasa a otra función para ofrecerle a esta segunda función un modo de volver a llamarnos más tarde.

Dicho de otro modo, al llamar a una función, le envío por parámetro otra función (un callback) esperando que la función llamada se encargue de ejecutar esa función callback. Resumiendo, los callbacks son funciones que contienen instrucciones que se invocarán cuando se complete un determinado proceso.

Por ejemplo, la función haceAlgo recibe un callback como argumento:

function haceAlgo(miCallback) {
	// hago algo y llamo al callback avisando que terminé
	miCallback();
}

// Invocamos al callback mediante una función inline
haceAlgo(function() {
	console.log('he acabado de hacer algo');
});

function ejemploCallback() {
	console.log('he realizado algo');
}

// Invocamos al callback con una función declarada con antelación
haceAlgo(ejemploCallback);

Pero los callbacks no sólo se utilizan para llamar a algo cuando termina una acción. Simplemente podemos tener distintos callbacks que se van llamando en determinados casos, es decir, como puntos de control sobre una función para facilitar el seguimiento de un workflow. La idea es disparar eventos en las funciones que llamaron “avisando” sobre lo que esta sucendiendo:

function haceAlgo(callbackPaso1, callbackPaso2, callbackTermino) {
	// instrucciones proceso 1
	callbackPaso1('proceso 1');
	// instrucciones proceso 2
	callbackPaso2('proceso 2');
	// instrucciones proceso Final
	callbackTermino('fin');
}

function paso1(quePaso) {
	console.log(quePaso);
}

function paso2(quePaso) {
	console.log(quePaso);
}

function termino(queHizo) {
	console.log(queHizo);
}

haceAlgo(paso1, paso2, termino);

De esta forma creamos funciones nombradas fuera de la llamada y éstas a su vez podrían disparar otros eventos (con callbacks) también.

Por último y no menos importante, los callbacks no son asíncronos, es decir, tras dispararse el callback, se ejecuta todo el código contenido y el control vuelve a la línea que lo disparó. En el ejemplo anterior dispara el callbackPaso1() y cuando este termina, continúa la ejecución disparando el callbackPaso2().

Esta duda se debe a que al tratar con elementos asíncronos, los callbacks se emplean para gestionar el orden de ejecución de dichas tareas asíncronas.

1.6.5. arguments

Además de los parámetros declarados, cada función recibe dos parámetros adiciones: this y arguments.

El parámetro adicional arguments nos da acceso a todos los argumentos recibidos mediante la invocación de la función, incluso los argumentos que sobraron y no se asignaron a parámetros. Esto nos permite escribir funciones que tratan un número indeterminado de parámetros.

Estos datos se almacenan en una estructura similar a un array, aunque realmente no lo sea. Pese a ello, si que tiene una propiedad length para obtener el número de parámetros y podemos acceder a cada elemento mediante la notación arguments[x], pero carece del resto de métodos que si ofrecen los arrays (que veremos en la siguiente sesión).

Por ejemplo, podemos crear una función que sume un número indeterminado de parámetros:

var suma = function() {
  var i, s=0;
  for (i=0; i < arguments.length; i+=1) {
    s += arguments[i];
  }
  return s;
};

console.log(suma(1, 2, 3, 4, 5));	// 15

1.7. Alcance

Recordemos que el alcance determina desde donde se puede acceder a una variable, es decir, donde nace y donde muere. Por un lado tenemos el alcance global, con el que cualquier variable o función global pueden ser invocada o accedida desde cualquier parte del código de la aplicación. En JavaScript, por defecto, todas las variables y funciones que definimos tienen alcance global.

Si definimos una variable dentro de una función, el alcance se conoce como de función, de modo que la variable vive mientras lo hace la función.

Aquella variable/función que definimos dentro de una función (padre) es local a la función pero global para las funciones anidadas (hijas) a la que hemos definido la función (padre). Por esto, más que alcance de función, se le conoce como alcance anidado.

Y así sucesivamente, podemos definir funciones dentro de funciones con alcance anidado en el hijo que serán accesibles por el nieto, pero no por el padre.

var varGlobal = "Esta es una variable global.";

var funcionGlobal = function(alfa) {
	var varLocal = "Esta es una variable local con alcance anidado";

	var funcionLocal = function() {
		var varLocal = "¡Hola Mundo!";
		console.log(varLocal);
		console.log(alfa);
	};

	funcionLocal();
	console.log(varLocal);
};

// console.log(varLocal)
funcionGlobal(2);
Autoevaluación

A partir del código anterior, ¿Qué mensajes saldrán por pantalla? [3] ¿Y si descomentamos la linea de console.log(varLocal) [4]?

Si queremos evitar tener colisiones con las variables locales, podemos usar una función expresión con invocación inmediata (IIFE). Para ello, tenemos que englobar el código a proteger dentro de una función, la cual se rodea con paréntesis y se invoca, es decir, del siguiente código:

(function() {
  // código
})();

De este modo, las variables que declaremos dentro serán locales a ésta función, y permitirá el uso de librería de terceros sin efectos colaterales con nuestras variables. Por lo tanto, si intentamos acceder a una variable local de una IIFE (también conocidos como closures anónimos) desde fuera obtendremos una excepción:

(function() {
	var a = 1;
	console.log(a);	// 1
})();
console.log(a);	// Uncaught ReferenceError: a is not defined
Autoevaluación

A partir del siguiente fragmento:

(function() {
	var a = b = 5;
})();
console.log(b);

¿Qué valor aparecerá por la consola? [5]

1.7.1. Hoisting

Al estudiar la declaración de variables, vimos el concepto de hoisting, el cual eleva las declaraciones encontradas en un bloque a la primera línea. Ahora que ya conocemos como funciona el alcance y las funciones lo estudiaremos en mayor profundidad.

En el ejemplo visto en Variables, teníamos el siguiente código:

"use strict";
var a = "global";
console.log(b);	// undefined
var b = 5;

La variable b era una variable que no se había declarado y pese a ello, aún usando el modo estricto, el intérprete no lanza ninguna excepción, porque la declaración se eleva al principio del bloque (no así la asignación, que permanece en su lugar).

Ahora veremos que dentro de una función, al referenciar a una variable nombrada de manera similar a una variable global, al hacer hoisting y declararla más tarde, la referencia apunta a la variable local.

"use strict";
var a = "global";
console.log(b); // undefined
var b = 5;
console.log(b); // 5

function hoisting() {
	console.log(b); // undefined (1)
	var b = 7;
	console.log(b); // 7
}
hoisting();
1 Si no hubiese hoisting se mostraría 5, pero al elevarse la declaración de la función, la variable local todavía no tiene ningún valor, de ahí el valor undefined

El siguiente fragmento es el equivalente, donde las declaraciones se elevan al principio del bloque pero las asignaciones e instrucciones se quedan en su sitio:

"use strict";
var a = "global";
var b;

function hoisting() {
	var b;
	console.log(b); // undefined
	b = 7;
	console.log(b); // 7
}

console.log(b); // undefined
b = 5;
console.log(b); // 5

hoisting();
Autoevaluación

¿Cual es el resultado de ejecutar el siguiente fragmento y por qué?: [6]

function prueba() {
	console.log(a);
	console.log(hola());

	var a = 1;
	function hola() {
		return 2;
	}
}
prueba();

1.8. Timers

JavaScript permite la invocación de funciones tras un lapso de tiempo y con repeticiones infinitas.

Para ejecutar una acción tras una duración determinada indicada en milisegundos tenemos la función setTimeout(funcion, tiempoMS). La llamada a setTimeout no bloquea la ejecución del resto de código.

(function() {
	var miFuncion = function() {
		console.log("Batman vuelve");
	};
	setTimeout(miFuncion, 2000);  (1)
}());
1 La función se ejecutará 2 segundos después de cargar la página

Si queremos que una función se ejecute de manera ininterrumpida con un intervalo de tiempo, podemos hacer una llamada con setTimeOut dentro de la función ya llamada lo que provoca su invocación de manera recursiva.

(function() {
	var velocidad = 2000,
	miFuncion = function() {
		console.log("Batman vuelve");
		setTimeout(miFuncion, velocidad);
	};
	var timer = setTimeout(miFuncion, velocidad);
	// cancelTimer(timer);
}());

Al ejecutar el código, tras 2 segundos, saldrá el mensaje por consola, y con cada iteración que se repetirá cada 2 segundos, se incrementará el contador mostrado por las Developer Tools.

Ejemplo de función iterada con un Timer

Aunque si lo que queremos es realizar una llamada a una función de manera repetida con un intervalo determinado, podemos hacer uso de la función setInterval(funcion, intervalo), la cual repetirá la ejecución de la función indicada.

Finalmente, si queremos detener un temporizador, mediante clearInterval(timer) cancelaremos la ejecución de la función.

Así pues, si reescribimos el ejemplo anterior haciendo uso de setInterval, tendremos:

(function() {
	miFuncion = function() {
		console.log("Batman vuelve");
	};
	var timer = setInterval(miFuncion, 2000);
}());

1.9. Gestión de errores

Para controlar el comportamiento erróneo de una aplicación, podemos hacer uso de las excepciones.

1.9.1. Capturando excepciones

¿Qué sucede cuando utilizamos a una variable que no esta declarada?

var a = 4;
a = b + 2;
console.log("Después del error");	// No se ejecuta nunca

Ya hemos visto que cuando se lanza un error, aparece por consola un mensaje informativo. Si queremos ver el código que ha provocado la excepción, en la consola podemos desplegar el mensaje de la excepción, y a su derecha podremos pulsar sobre el archivo:linea y nos mostrará la línea en cuestión que ha provocado el fallo en el panel de Sources:

Código tras pulsar sobre la excepción

Para evitar estos errores, JavaScript ofrece un mecanismo try / catch / finally similar al que ofrece Java. Al capturar una excepción, podemos acceder a la propiedad message de la excepción para averiguar el error.

Por ejemplo, si reescribimos el ejemplo anterior y capturamos la excepción tendremos:

try {
	var a = 4;
	a = b + 2;
	console.log("Después del error");
} catch(err) {
	console.error("Error en try/catch " +  err.message);
} finally {
	console.log("Pase lo que pase, llegamos al finally");
}

La cuarta línea, que hace console.log("Después del error"), nunca se ejecutará porque al intentar sumarle 2 a una variable que no hemos declarado, se lanza una excepción que hemos capturado.

Consola tras try/catch

1.9.2. Lanzando excepciones

Si queremos que nuestro código lance una excepción, usaremos la instrucción throw, pudiendo bien lanzar una cadena de texto que será el mensaje de error, o bien crear un objeto Error.

function ecuacion2grado(a,b,c) {
	var aux = b*b-4*a*c;
	if (aux < 0) {
		throw "Raíz Negativa";
		// throw new Error("Raíz Negativa");
	}
	// resto del código
}

1.9.3. Debug

Ya vimos en el apartado de las [_dev_tools] como podemos introducir breakpoints para depurar el código o parar la ejecución del código cuando se produce una excepción.

Una gran desconocida es la instrucción debugger, la cual funciona de manera similar a un breakpoint pero a nivel de código, de modo que paraliza la ejecución del código e invoca al debugger (si existe alguno, si no, no realiza nada) y pausa la ejecución de la aplicación.

function funcionQueDaProblemas() {
	debugger;
	// código que funciona de manera aleatoria
}

1.9.4. Errores comunes

A la hora de escribir código JavaScript, los errores más comunes son:

  • No cerrar una cadena, olvidando las comillas de cierre.

  • Olvidar el punto y coma tras asignar una función anónima a una variable/propiedad.

  • Invocar a una función, método o variable que no existe.

  • Errores de sintaxis, por ejemplo, document.getElementByID("miId");

  • Referenciar un elemento del DOM que todavía no se ha cargado.

  • En una condición, usar una asignación (=) en vez de una comparación (== o ===). Aunque la aplicación no se va a quejar, siempre será verdadera.

  • Pasar a una función menos parámetros de los necesarios no provoca ningún error, pero podemos obtener resultados inesperados.

Muchos de estos errores se pueden evitar haciendo uso del modo estricto de ECMAScript 5.

1.10. Ejercicios

Todos los ejercicios del módulo deben estar en modo estricto ("use strict") y cumplir la nomeclatura de nombrado de archivos.

El repositorio a clonar es java_ua/ejercicios-js-expertojava.

1.10.1. (0.4 ptos) Ejercicio 11. toCani

Crear una función que reciba una cadena y la devuelva transformada en Cani. Por ejemplo, si le pasamos a la función la cadena "una cadena cani es como esta" obtendremos "UnA KaDeNa kAnI Es kOmO EsTaHHH". Para ello, hay que alternar el uso de MAYÚSCULAS y minúsculas, sustituir la letra C por la K y añadir tres letras H al final.

La función se almacenará en una archivo denominado ej11.js, y tendrá la siguiente definición:

function toCani(cadena) {}

1.10.2. (0.6 ptos) Ejercicio 12. Temporizador

Crear una función temporizador que reciba como parámetro los minutos y segundos de duración del mismo, de modo, que cada segundo mostrará por consola el tiempo que le queda al temporizador hasta llegar a 0.

La función recibirá dos parámetros, con los minutos y los segundos, pero en el caso que sólo le pasemos un parámetro, considerará que son los segundos desde donde comenzará la cuenta atrás.

Por ejemplo:

temporizador(77);	// le pasamos 77 segundos
temporizador(2,50);	// le pasamos 2 minutos y 50 segundos

Si alguno de los valores que recibe como parámetros son negativos o de un tipo inesperado, la función debe lanzar una excepción informando del problema.

La función se almacenará en una archivo denominado ej12.js, y tendrá la siguiente definición:

function temporizador(minutos, segundos) {}

1. 1 — 1 — 2015
2. 7
3. "¡Hola Mundo!", "2" y "Esta es una variable local con alcance anidado"
4. No saldría nada porque tendríamos un error de JavaScript al no ser visible varLocal
5. 5, porque a si que es local a la función, pero b es global. Si la ejecutásemos mediante el modo estricto, obtendríamos un error de referencia
6. undefined y 2, ya que tanto la variable como la función hacen hoisting. La variable se declara pero no toma valor hasta la asignación, en cambio, la función si que se declara y se puede invocar.