Curso de formación del PAS: 1 Introducción al lenguaje de programación Javascript

<<<<<<< HEAD ======= <<<<<<< HEAD ======= >>>>>>> c978b6bdb2f4128e253ffcce1e2aa34def6af0c6 >>>>>>> 3bd00d4f0b77bb8b520e874d7ea3febf1d4bf23b

1.1 ¿Javascript? Pero si es muy fácil #

Javascript es un lenguaje interpretado, que se ejecuta principalmente en el navegador, y fuera de él casi no se usa. No está diseñado para realizar cálculo intensivo, como el Fortran (nadie en su sano juicio enseñaría hoy Fortran, de todas maneras), ni hay ningún tipo de sistema operativo o procesador o lo que sea optimizado para trabajar con Javascript. Pero la cuestión es que los navegadores, que son los nuevos sistemas operativos, sí que lo están. Y por eso es importante aprender a trabajar con él.

Por eso, frente a otros lenguajes, Javascript tiene una ventaja. Está en todas partes. No hay ordenador sin navegador, ni hay navegador sin Javascript (salvo en móviles y algunos PDAs). Se puede ejecutar hasta en el navegador Opera que viene con la Wii. Y no cabe duda que una de las ventajas que ofrece la computación de altas prestaciones en su vertiente distribuida es el poder abarcar cuantos más dispositivos mejor.

El que esté en tantos dispositivos se debe en parte a que ha sido definido como un estándar ECMA . El hecho de que sea un estándar da potencialmente lugar a muchas implementaciones diferentes, que son, además, independientes del fabricante. Y, por otro lado, todas las implementaciones suelen usar un conjunto de objetos estándar (que no son estrictamente parte de un lenguaje) que se pueden usar para añadir funcionalidad a una aplicación. Muchas aplicaciones complejas, como GMail o Google docs dependen de estos objetos para crear aplicaciones en la web que se comporten como si se ejecutaran en un sistema operativo nativo.

Además, a diferencia de otros lenguajes, es muy fácil crear una aplicación distribuida con Javascript (ojo, no digo en Javascript, porque hará falta algún otro lenguaje de programación en la parte del servidor, aunque cada vez más el servidor se puede programar también en JS). Tanto por su integración con el navegador (parte inseparable de la web, una arquitectura cliente-servidor) como por los objetos que suele tener el mismo, crear una aplicación cliente-servidor es casi trivial y ha dado lugar a un estilo de programación denominado AJAX.

Por lo que he probado, SpiderMonkey, Rhino y KJSCMD se instalan fácilmente en Ubuntu; no hay más que hacer aptitude search javascript y salen esos y alguno más. Fedora Core es un poco más rácano en cuanto a entornos: aunque los puedes instalar bajándote los fuentes y compilando, el que está disponible en los repositorios es uno llamado sólo js, aparentemente el SpiderMonkey. Para instalarlo, escribir $yum install js$. Para los ejemplos que vienen en este tema, sustituir #!/usr/bin/smjs por #!/usr/bin/js . En cuanto a Windows, las últimas versiones permiten ejecutar un dialecto de JS simplemente ejecutando, desde la línea de órdenes, cscript loquesea.js; se puede ejecutar directamente desde el Explorer también, pero habrá que tener cuidado con mantener la ventana de ejecución abierta para que se vean los resultados.

Para ejecutar Javascript no hace falta más que un navegador, pero también hay entornos para trabajar con él de forma autónoma, tal como el Mozilla SpiderMonkey y el Rhino, éste último basado en Java. Conviene instalar alguno de ellos para poder ejecutar los programas de ejemplo; por ejemplo, para instalar el primero habrá que ejecutar (desde la consola de administrador o superusuario):

bash% apt-get install smjs

En cuanto a los navegadores, puede que haga falta activar Javascript para que lo entiendan. Por ejemplo, en Firefox hay que activarlo usando Edit -> Preferencias -> Contenido -> Activar Javascript.

Si usas Eclipse, puede que te venga bien el plugin para Eclipse, pero, la verdad, yo no he conseguido que haga gran cosa. Igual al atento lector le funciona correctamente. Por otro lado, el xemacs va de lujo.

En realidad, esa integración con el navegador puede que haya estorbado a la aceptación de JS como un lenguaje decente; tampoco ha ayudado que la mayoría de las librerías que se hayan desarrollado estén relacionadas precisamente con la Web, y no tenga una librería madura para, pongamos por caso, acceder a bases de datos (aunque, poco a poco, también las va habiendo). Pero también ha permitido que el lenguaje siga siendo pequeño, y se haya desarrollado principalmente a base de añadir objetos y clases externas al mismo. En resumen, que en el marco de eso que se ha venido en llamar web 2.0 JS se ha convertido en adulto (y mucho más desde que ha sido usado para crear un emulador del ordenador clásico Commodore 64), y merece la pena estudiarlo como cualquier otro lenguaje.

Por último, como está el 1st Javascript Editor, que es de pago, pero pueden conseguirse versiones de evaluación gratuitas.

Bloque 1.1

  1. Comprobar la existencia de JavaScript en el navegador que se use habitualmente, y activarlo en caso necesario. Enviar un correo electrónico con la captura de pantalla correspondiente.
  2. Comprobar la existencia de intérpretes de JavaScript fuera del navegador, desde el intérprete de comandos. Instalar uno en caso necesario. Enviar una captura de pantalla que atestigüe la instalación.

1.2 Ejecutando nuestro primer programa en Javascript #

El siguiente programa, ejecutable desde la línea de órdenes, imprime el clásico Hola, Mundo en Linux (al menos en Fedora y Ubuntu):

#!/usr/bin/smjs print( 'Hola, Mundo' );

En realidad, el JS es bastante parecido al C, y, para el caso, también al Java. Este programa producirá (previo permiso de ejecución, es decir, chmod +x hola.js): jmerelo@vega:~/txt/docencia/AAP/Temario$ code/hola.js Hola, Mundo print es una orden del intérprete, que no actuará de la misma forma en el navegador (en concreto, intentará sacar por impresora la página en la que se encuentre); se trata, por tanto, de una característica específica, que puede funcionar de forma diferente en otros intérpretes. En este caso, el intérprete se llama SpiderMonkey, y todas las opciones, comandos expecíficos y demás los poddemos encontrar en este manual en el sitio web Mozilla. Por ejemplo, vemos la opción -e, que nos permite ejecutar directamente una orden desde la línea de comandos: jmerelo@sheldon:~/public_html/tutoriales/js$ smjs -e 'print("Hola, Mundo")' Hola, Mundo

En Windows y usando la línea de órdenes se debe sustituir print por WScript.echo

Bloque 1.2

  1. Ejecutar este programa u otro similar desde la línea de órdenes, y enviar una captura de pantalla para probarlo.
La página de SpiderMonkey incluye un apartado de descargas donde hay todo tipo de versiones compiladas de este intérprete, para todos los sistemas operativos.

Este mismo intérprete se puede usar de forma interactiva para ejecutar órdenes que se escriban sobre la marcha escribiendo js -i. Por ejemplo jmerelo@sheldon:~/public_html/tutoriales/js$ js -i js> print("Hola, Mundo") Hola, Mundo js> Para terminar, se usa un esperable quit() para salir de este modo interactivo. help() también funciona, pero no hay muchas más órdenes que se puedan usar, salvo load: js> load('code/hola-sin-poundbang.js') Hola, Mundo

Bloque 1.3

  1. Capturar una sesión en el intérprete interactivo de JS en el sistema operativo en el que se esté trabajando.

La ejecución desde el navegador se hace de otra forma. Para empezar, si el código no es muy extenso se puede hacer directamente desde la consola de Javascript, que se obtiene de diferentes formas: en Chrome, por ejemplo, se pulsa sobre el icono de la página, y "Opciones para desarrolladores" seguido de "consola Javascript". Lo vemos en este ejemplo (darle a ver fuente para ver el código) donde se incluye el programa en JS de esta forma:

<script type='text/javascript' src='hola.js'></script>

El problema es que, en este caso, la orden print se interpreta como impresión por impresora, y habrá que cambiarla por otra que signifique lo mismo, escribir en el dispositivo de salida que se esté usando:

document.writeln('Hola, Mundo')

Lo que también se puede escribir directamente así:

<script type='text/javascript'>document.writeln('Hola, Mundo')</script>

La consola de JavaScript integrada en la mayoría de los navegadores actúa también como intérprete interactivo del mismo; tienen diferente funcionalidad dependiendo del navegador. Por ejemplo, en Chrome permite llevar a cabo una depuración bastante extensa; el Firefox la consola instalada por defecto es útil para depurar y ejecutar cosas sobre la marcha, pero extensiones como FireBug permiten hacer una depuración mucho más precisa. En cualquiera de ellos podemos usar las funciones del navegador para, por ejemplo, crear una ventana que presente un mensaje de esta forma alert('Vivaspaña'), que presentará una ventana con un pequeño botón de "Aceptar", que se puede pulsar para hacerlo desaparecer.

Bloque 1.4

  1. Desde la consola JS del navegador, crear una ventana de alerta como se ha mostrado anteriormente. Hacer una captura de pantalla que incluya la ventana creada y la consola JS.

Las anteriores no son las únicas opciones para ejecutar JS; como el fuente del intérprete de Mozilla están disponibles, se han construido a partir de él todo tipo de programas. Uno de ellos es JSDB, creado principalmente para acceder a bases de datos, con unos drivers de ODBC incluídos. Tiene ligeras diferencias con el SpiderMonkey mostrado anteriormente, y además, puede ser usado como intérprete independiente más que como una herramienta de desarrollo. El ejemplo visto anteriormente funcionaría de esta forma: jmerelo@sheldon:~/soft/jsdb$ ./jsdb js>writeln('hola, mundo') hola, mundo Incluso permite compilar los programas y crear ficheros ejecutables a partir del código JS.

En cualquier caso, hay una amplia variedad de intérpretes disponibles, y la elección dependerá de la velocidad que necesite uno, las funciones que se requieran (por ejemplo, Rhino permite acceder a la librería estándar Java), o el sistema operativo en el que se encuentre uno.

Bloque 1.5

  1. Descargar algún intérprete de JavaScript tal como GlueScript y mostrar alguno de los ejemplos que figuren en su página web funcionando.

1.3 Variables y otras estructuras #

Vistas ya las mil y una formas de escribir cosas en la pantalla, procedamos a temas más escabrosos, como lo que viene siendo hacer algo realmente. Por ejemplo, un bucle que cree una tabla HTML, como se hace en el siguiente programilla:

var tabla="table"; var celda="td"; var fila="tr"; var matriz = [11,22,33]; print( "<"+tabla+">"); for (i in matriz ) { print( "<"+fila+">"); for ( j in matriz ) { print ("<"+celda+">"+matriz[i]*matriz[j]+"</"+celda+">"); } print ("</"+fila+">\n"); } print ("</"+tabla+">");

Este programa tiene dos bucles anidados, que imprimen un producto dentro de una tabla. La salida tras ejecutarlo con ./tabla.js (o forma equivalente en Windows). será tal que así (ver el fuente para la estructura):

121 242 363
242 484 726
363 726 1089

Bloque 1.6

  1. Hacer los cambios necesarios y ejecutar ese programa usando otro intérprete: cscript para Windows, JSDB o en el navegador.

El programa es más complicado de lo que parece. Para declarar variables en JS se usa el genérico var, aunque también se pueden declarar tipos específicos. Para no pillarnos los dedos, usamos var. En realidad, tampoco hace falta: simplemente usando una variable aparece mágicamente. Con las matrices ocurre igual (es decir, se declaran y se les asigna valor directamente) : matriz lo es, y simplemente se declaran sus valores entre corchetes.

Los bucles tienen la apariencia habitual, y se pueden usar igual que en C (es decir, con el clásico for i=0, i <...), pero haremos un bucle que recorra la matriz, usando un iterador i, que en este caso se comporta como una variable de bucle de las de toda la vida. Usamos el + para concatenación de cadenas, y poco más. El resto es como el C, o el Java. De hecho, se pueden usar los bucles clásicos con comparación e incremento, como se muestra en tabla1.js:

for (i=1; i<=3; i++ ) { print( "<"+fila+">"); for ( j=1; j<=3; j++ ) { print ("<"+celda+">"+i*j+"</"+celda+">"); } print ("</"+fila+">\n"); }

Aunque queda un poco torpe tanto <>... vamos a reducir un poco el programa, haciéndolo más elegante (aunque más largo: no se puede tener todo):

var matriz = [1,2,3]; print( marca('table')); for (i in matriz ) { print( marca( 'tr' )); for ( j in matriz ) { print ( celda(matriz[i]*matriz[j])); } print ( finmarca('tr')); } print (finmarca('table')); function marca( m ) { return "<"+m+">"; } function finmarca( m ) { return "</"+m+">"; } function celda( contenido ) { return marca("td")+contenido+finmarca("td"); }

La principal diferencia con respecto al anterior es el uso de funciones. Las funciones en JS tienen una estructura bastante clásica: function nombre-de-función (param1, param2...). Una vez más, se nota que JS no es un lenguaje con tipos fuertes, pudiendo pasar los parámetros sin tipo, y adaptándose dentro de la función al tipo necesario. Y estos parámetros se pasan por valor, es decir, que las modificaciones al parámetro formal no se trasladan a la variable que se use. Además, se pueden declarar donde a uno le dé la gana. Para llamarlas tampoco hay que hacer nada especial, se usa el clásico paréntesis. La salida es exactamente la misma que antes. El también clásico return devuelve un valor.

El ámbito de las variables es el bloque donde aparecen o se declaran, pero hay que tener en cuenta que, a efectos de JS, una página web es un programa. Se pueden declarar variables en la cabecera del documento HTML, y estarán accesibles en cualquier otro sitio, siempre que esté mas adelante en el documento (porque los programas javascript se ejecutan en el mismo orden en el que se carga el documento). También habrá que tener en cuenta, en caso de que esté incluido en una página web, que aunque la declaración de una subrutina afecte a todo el programa, puede que esa parte de la página no se haya cargado todavía, con lo que no estará disponible. Una vez más, la programación distribuida no es totalmente igual a la programación en otros lenguajes.

Bloque 1.7

  1. Hacer una función que devuelva una tabla en HTML con el número de filas y columnas que se le pasen como parámetro.

1.4 Clases y objetos en Javascript #

Javascript es un lenguaje basado en objetos, aunque un tanto peculiar; en realidad, de casi todas las características de un lenguaje orientado a objetos, solo tiene los objetos, e incluso estos son de aquella manera. Por eso no es exactamente dirigido a objetos u orientado a objetos. Las características las veremos en el siguiente programa, que podría servir para hacer quinielas.

var equipos= new Array('Madrid', 'Barça', 'Atleti', 'Geta', 'Betis', 'Depor', 'Sevilla', 'Graná'); // Definición de la clase Partido function Partido(local,visitante) { this.local = local; this.visitante=visitante; this.resultado=null; } var midsize = equipos.length/2; var quiniela = new Array( midsize ); for ( i=0; i < midsize ; i++ ) { var equipo1 = equipos.splice(Math.floor( equipos.length*Math.random()) , 1); var equipo2 = equipos.splice(Math.floor( equipos.length*Math.random()), 1); quiniela[i] = new Partido( equipo1, equipo2 ); } for ( i in quiniela ) { print( "Partido " + (parseInt(i)+1)+": " + quiniela[i].local + " - " + quiniela[i].visitante); }

Con lo primero que nos enfrentamos es con una nueva forma de definir una matriz o Array: ya que sabemos que JS es OO, pues usamos una forma OO de definirlo, mediante la orden new, que, como en Java y en C++, crea un nuevo objeto llamando al constructor del mismo. En este caso le pasamos directamente los elementos que constituyen el vector o array, pero podríamos haberle pasado el tamaño de esta forma: var myArray = new Array(33); Los objetos así creados son objetos de pleno derecho, y se puede acceder a sus propiedades y métodos usando también una sintaxis clásica: el puntito . tras el nombre de la variable. Por ejemplo, myArray.length devolvería el tamaño de la matriz

Pero como lo que se trata en este programa es de definir nosotros una clase, lo hacemos en las líneas siguientes, en la función Partido, que convencionalmente ponemos en mayúscula, para indicar que es un nombre de clase. En realidad, una clase en JS es una función dentro de la cual se le asigna un valor a la variable this, como en esta: cada uno de los elementos de la variable this será una variable de instancia. Como se ve, aquí no hay encapsulación ni perrito que le ladre.

Lo que vamos a crear es un vector de estos partidos, e irle asignando valores extraidos aleatoriamente. Mientras tanto, usamos los métodos que llevan objetos de clases estándar JS; igual que otros lenguajes tienen librería estándar, JS tiene clases estándar: Array, que ya hemos visto, y Math. Lo que usamos de Math son métodos de clase, no de instancia, tales como Math.random, que genera un número aleatorio entre 0 y 1. También se usa un método de instancia, splice, que extrae una parte del vector de equipos; extraemos el seleccionado, para que no dé por saco mientras generamos el resto de la guiniela.

Y el objeto lo creamos mediante una clásica llamada:

quiniela[i] = new Partido( equipo1, equipo2 );

Más adelante usamos un bucle in para escribir los valores de cada uno de los partidos; las variables no están encapsuladas, así que se puede acceder a ellas directamente: partido.local, por ejemplo. Hay también un pequeño truco: el uso de parseInt dentro de print para que se interprete i como un número entero, no como una cadena, y, por tanto, el + que lo sigue como una suma normal y no una concatenación de cadenas. i+1 daría 11, mientras que parseInt(i)+1 dará 2.

Y el resultado, aleatorio por supuesto, será algo así como esto : Partido 0: Graná - Atleti Partido 1: Madrid - Depor Partido 2: Betis - Barça Partido 3: Sevilla - Geta

La declaración de métodos de clase se hace más o menos de la misma forma, que no es muy ortodoxa, pero es la que hay. Lo veremos en el siguiente programa, del que sacamos el fragmento más interesante:

// Definición de la clase Partido function Partido(local,visitante) { this.local = local; this.visitante=visitante; this.resultado=null; //Estas son variables this.setResultado = setResultado; this.toString = toString; // y estos son métodos de clase } function setResultado( esteResultado ) { if ( esteResultado == '1' || esteResultado=='x' || esteResultado=='2' ) // sólo valores válidos this.resultado = esteResultado; } function toString() { return "Partido " + i + ": " + quiniela[i].local + " - " + quiniela[i].visitante + " = "+ this.resultado; }

Añadimos un par de funciones, y para que pertenezcan a la clase, hala, con un this por aquí y un this por allá, solucionado. Es de buen gusto llamar al método con el mismo nombre que la función, para no despistarse; también seguir una cierta convención para ponerles nombres: get, o to, o cosas por el estilo. Lo que no es de recibo es llamar a una función que cambia el valor de la variable igual que la variable, porque entonces nos liamos. Y como ya hemos visto antes como se llama a los métodos de instancia, pues listos. No se hable más.

Bloque 1.8

  1. Hacer una clase que corresponda a una fila de una tabla, con número de columnas variable, y un programa que permita crear e imprimir una tabla usándolo.

1.5 Matrices asociativas #

Pero hay más matrices, aparte de las lineales: JS, como muchos otros lenguajes, permite trabajar con matrices asociativas (también llamadas diccionarios o hashes). En una matriz asociativa, la clave es una cadena, en vez de un número, lo que le da mucha más flexibilidad a la hora de almacenar información. En un vector, se accede a cada uno de los elementos del vector a través de un índice numérico, y eso implica también un orden en su estructura (y, a veces, una continuidad en su almacenamiento, aunque no necesariamente tiene que ser así). Es decir, un vector lineal es un grupo de parejas (0, valor[0], 1, valor[1],...., n, valor[n]). De hecho, como los números suelen ser sucesivos, muchas veces se dan por sobreentendidos, de forma que para trabajar con un vector (asignarle valores, por ejemplo) sólo se usan sus valores: valor[0], valor[1],..., valor[n].

Sin embargo, una matriz asociativa, diccionario o Tabla_hash (o simplemente hash) está compuesto por una serie de pares (cadena alfanumérica, valor): (cadena1, valor1, cadena2,valor2... cadenan, valorn). Los valores están asociados a su cadena correspondiente; de forma que la forma de acceder a los valores es a través de la cadena alfanumérica usada para indexarlos, que se suele denominar clave (key). Casi todos los lenguajes de programación tienen alguna forma de usar estas matrices asociativas. Por ejemplo, en Perl: my %matrizAsociativa; # % para matrices asociativas $matrizAsociativa{'variable'}='Valor'; # { para las claves print $matrizAsociativa{'variable'}; devolvería Valor.

En Ruby funciona de forma similar: usando corchetes para acceder al valor, y usando => para asignarlo: matriz_asociativa = { 'esta_clave' => 'este_valor'} puts matriz_asociativa['esta_clave']

Las usaremos en el siguiente programa, que genera aleatoriamente 10 jornadas de una liga, y asigna puntuación según los resultados:

load('Partido.js'); var equipos= new Array('Madrid', 'Barça', 'Atleti', 'Geta', 'Betis', 'Depor', 'Sevilla', 'Graná'); function jornada( estosEquipos ) { var equiposAqui = new Array; equiposAqui = equiposAqui.concat(estosEquipos); var midsize = equiposAqui.length/2; var quiniela = new Array( midsize ); var unox2 = new Array( '1','x','2'); for ( var i=0; i < midsize ; i++ ) { var equipo1 = equiposAqui.splice(Math.floor( equiposAqui.length*Math.random()) , 1); var equipo2 = equiposAqui.splice(Math.floor( equiposAqui.length*Math.random()), 1); quiniela[i] = new Partido( equipo1, equipo2 ); quiniela[i].setResultado( unox2[Math.floor( 3*Math.random()) ]); } return quiniela; } var quinielas = new Array; for ( var i = 0; i < 10; i ++ ) { quinielas[i] = jornada( equipos ); } var resultados= new Array; for ( var i in equipos ) { resultados[equipos[i]]=0; } for ( var i = 0; i < quinielas.length; i ++ ) { for ( var j = 0;j < quinielas[i].length; j ++ ) { var local = quinielas[i][j].local; var visitante = quinielas[i][j].visitante; var resultado = quinielas[i][j].resultado; if ( resultado == 1 ) { resultados[local]+=3; } else if ( resultado == 'x' ) { resultados[local]+=1; resultados[visitante]+=1; } else { // resultado == 2 resultados[visitante]+=3 } } } for ( var i in resultados ) { print( i + ": " + resultados[i]) }

En parte, este programa es similar a los anteriores: la parte que generaba cada jornada está ahora en una función, que devuelve un array de resultados, que se guardan en el array quinielas. Hemos sacado, además, la definición de la clase Partido a un fichero externo, que cargamos con load. Por otro lado, como a la función jornada se le pasa una referencia al vector con los equipos, tenemos que copiarlo a una variable local, definiéndola (equiposAqui), y concatenándole (concat) el vector que se le pasa por valor, que es igual que copiarlo, pero seo hace en una sola orden.

El truco está a partir de la definición de la variable resultados. Esta variable es una matriz asociativa que contendrá la puntuación de los equipos, y estará indexada por el nombre del equipo. Se declara igual que los demás arrays, y, para inicializarlo, vamos extrayendo los valores del vector de equipos, y usándolos como clave: resultados[equipos[i]]=0; equipos[i] valdrá sucesivamente Barça, Graná... y así se irán inicializando a 0 los valores correspondientes. Si no se inicializan, la primera vez que se usa una variable tiene el valor NaN, con el que no se puede hacer nada. Es así de arisco.

Más adelante se va recorriendo en un bucle doble los partidos de cada una de las jornadas, y asignando puntuación dependiendo del resultado de la quiniela. Se usa la construcción if... else if ... else, que funciona de la forma habitual, aunque también podríamos haber usado switch, como en el siguiente programa, que en lo único que cambia es en estas líneas:

switch (resultado) { case '1': resultados[local]+=3; break; case 'x': resultados[local]+=1; resultados[visitante]+=1; break; default: resultados[visitante]+=3; } }

y que viene a ser como el anterior, pero con cases en vez de ifs. Vamos, tres cuartos de lo mismo.

Bloque 1.9

  1. Modificar el programa anterior (cualquiera de ellos) para que contabilice el número de partidos ganados, empatados y periddos.

1.6 Manejando Objetos y Clases Estándar #

En realidad, todo en Javascript es un objeto, y especialmente los vectores: tanto los vectores tradicionales como las matrices asociativas como los objetos se representan internamente de la misma forma, y te puedes referir a ellos de diferentes maneras. Vamos a usar el depurador interactivo para verlo, ejecutando simplemente rhino, smjs o kjs en la línea de comandos. Una vez hecho, tecleamos las siguientes órdenes: js> foo = new Array js> foo.cero='Cero' Cero js> foo[1] = 'Uno' Uno js> foo['dos'] = 'Dos' Dos js> foo.dos Dos js> foo['cero'] Cero js> for ( i in foo) { print(foo[i]);} Cero Uno Dos Hay que teclear lo que se encuentra detrás de js>; cada segunda línea es la respuesta del intérprete a nuestras órdenes. En la primera, creamos un vector, y le asignamos valor a tres elementos de formas diferentes: usando la notación de objeto (con el .) para el 0, la notación de vector para el 1, y la notación de matriz asociativa para el 2. Luego se ve que, independientemente de cómo se haya asignado el valor, se puede usar cualquier otra notación para acceder al elemento; y, finalmente, vemos como se puede recorrer de forma uniforme el array usando sus componentes mediante la orden in.

Por eso precisamente, hay que tener un poco de cuidado con estos arrays asociativos que se comportan un poco como les da la gana. Es conveniente usar para ellos Object, que es lo que son, en vez de Array. De hecho, si en lo anterior sustituimos Array por Object dará exactamente el mismo resultado. Por eso se consideran perniciosas los arrays asociativos en JS, pero es simplemente una cuestión de convención.

No todo va a ser público en un objeto; también pueden tener su intimidad guardada en variables privadas:

js> function Foo( bar ) { this.bar = bar; var privada = 7;} js> var este_foo = new Foo( 'correquetepillo' ); js> print(este_foo.bar) correquetepillo js> print(este_foo.privada) undefined

Es tan secreta, de hecho, que ni siquiera te dice que no existe: simplemente que su valor está indefinido.

El propio estándard Javascript (ECMAScript) define una serie de clases que se pueden instanciar, que corresponderían a la librería estándar (o librería estándar de clases) en otros lenguajes. Una de ellas ya la hemos visto: la clase Array. Otra es la clase String, que se usa para manejar cadenas alfanuméricas, chorros de 0s y 1s. js> var cadena = new String("1"); js> print(cadena + 1) 11 La clase String tiene una serie de métodos que permiten hacer lo habitual con las cadenas: encadenarlas, dividirlas, y buscar cosas. js> var nombres = "Pedro, Lucas, Juan".split(", "); js> print(nombres[0]) Pedro En este caso, split es un método de la clase String, y lo estamos aplicando directamente sobre la cadena "Pedro, Lucas, Juan", que, de por si, es un objeto de esa clase. split divide la cadena usando los caracteres que le pasamos, y da lugar a un Array con tantos componentes como resulte.

De camino, podría haber una clase para escribir y leer ficheros, porque con el rato que llevamos, todavía no hemos visto ninguna, y, además, cualquier lenguaje decente escribe y lee ficheros. Es más, es que muchos no hacen otra cosa, ¿no? Pues no. El estándar JS no define ningún tipo de rutina de E/S. Pero si usamos el intérprete Rhino (en vez de SpiderMonkey, que es el que hemos venido usando), podemos usar clases de Java directamente, lo que complica terriblemente el programa, pero ahí está, de todas formas:

// ejecutar con rhino lee_quiniela.js <argumento> load('Partido.js'); var FileReader = java.io.FileReader; var BufferedReader =java.io.BufferedReader; var file_name = arguments[0]; var f = new FileReader(file_name); var br = new BufferedReader( f ); var resultados= new Array; var line = new String; while ((line = br.readLine()) != null) { var estaLinea = new String( line ); var resultado = estaLinea.split(" "); switch (resultado[2]) { case '1': if ( resultados[resultado[0]] ) { resultados[resultado[0]]+=3; } else { resultados[resultado[0]]=3; } break; case 'x': if ( resultados[resultado[0]] ) { resultados[resultado[0]]+=1; } else { resultados[resultado[0]]=1; } if ( resultados[resultado[1]] ) { resultados[resultado[1]]+=1; } else { resultados[resultado[1]]=1; } break; default: if ( resultados[resultado[1]] ) { resultados[resultado[1]]+=3; } else { resultados[resultado[1]]=3; } break } } for ( var i in resultados ) { print( i + ": " + resultados[i]) }

El programa es bastante similar al anterior, pero lee de fichero en vez de generar los resultados aleatoriamente. Y lo lee aproximadamente de la misma forma a como se haría en Java. Por eso hay que usar Rhino, que permite usar las clases de la librería estándar de Java de forma nativa. En este caso usamos dos clases: FileReader y BufferedReader, para poder leer de línea en línea. La única diferencia a como se haría en Java es que hay que pasar la línea leida de un objeto String de Java a un objeto String de JS, que es lo que se hace en var estaLinea = new String( line );. También sale un poco más largo porque, como indicamos en el programa de más arriba, los elementos de un Array hay que inicializarlos; en cada caso del switch comprobamos si están inicializados o no antes de sumarles; si no lo está, le asignamos el valor directamente.

En realidad, las primeras líneas lo único que hacen es declarar un alias para las clases de Java. En JS una variable puede contener cualquier cosa, incluso una clase. Así acortamos el nombre, y parecen más de javascrí.

La otra diferencia es también cómo se ejecuta el fichero: rhino lee_quiniela.js quiniela.datos para ejecutarlo sobre el fichero quiniela.datos, que dará el resultado siguiente: Elche: 1 Atleti: 3 Cai: 1 Athleti: 6 Bar~a: 1 Madrid: 2 H~rcules: 1 Queda con esto más o menos claro que para ir donde nadie ha ido antes con JS, hay que meterse un poco en Java. Pero no siempre.

Aunque no lo parezca, JS es todavía un lenguaje joven, al que le faltan gran cantidad de librerías básicas, y, especialmente, una forma centralizada de empaquetar, probar y distribuir esas librerías, como CPAN para Perl o GEMs para Ruby. Hay algo por el estilo, llamado OpenJSAN, de JS Archive Network. Para instalarlo hace falta Perl, y sólo hay unas pocas librerías todavía. Algunas muy útiles, pero siguen siendo unas pocas. Otras, como la amplia JSlib, sólo va en Windows.

La que si es popular es Prototype, una librería que se usa principalmente en conjunción con RoR y AJAX, pero resulta que necesita ejecutarse dentro del navegador, porque usa objetos del mismo (como document, por ejemplo). Así que lo dejaremos para más adelante (si acaso).

Bloque 1.10

  1. Hacer algún programa en JS que use cualquier clase de Java conocida.

1.7 Objetos para el camino #

Lo interesante de los objetos en JS es que hay una forma muy fácil de serializarlos (es decir, convertirlos en texto u otro formato de forma que se puedan intercambiar fácilmente con otros programas a través de la red); a este formato se le denomina JSON (JavaScript Object Notation). Y como en realidad, tal como se ha visto en el apartado anterior, todo es un objeto en JS, se puede usar esta notación para asignar valores prácticamente a cualquier cosa. Vamos a usar una vez más el intérprete en modo interactivo para ver un ejemplo: js> var objeto = { Madrid : 25, Atleti: 33, Ponferradina: 44 }; js> for (i in objeto) { print( i + " : "+ objeto[i] )}; Madrid : 25 Atleti : 33 Ponferradina : 44 Más fácil no puede ser. Se le asigna valor a un objeto con el formato clave : valor (con coma al final), de la misma forma que se haría a un array asociativo. Además, se pueden crear objetos sobre la marcha y asignárselos a una variable cuyo valor se cree también sobre la marcha: js> eval("var objeto2 = { Madrid : 25, Atleti: 33, Ponferradina: 44 }"); js> for (i in objeto2) { print( i + " : "+ objeto[i] )}; Madrid : 25 Atleti : 33 Ponferradina : 44 donde usamos eval, que interpreta una expresión en Javascript como si del propio intérprete se tratara. Las expresiones se pueden anidar, para dar lugar a objetos más complejos js> eval("var objeto2 = { Madrid : 25, Atleti: 33, Ponferradina: { casa: 33, fuera: 44} }"); js> for (i in objeto2) { print( i + " : "+ objeto2[i] )}; Madrid : 25 Atleti : 33 Ponferradina : [object Object] Que parece más raro de la cuenta, pero que, con un poco de código, se podría también imprimir.

Y JSON, precisamente, es uno de los formatos de intercambio, el más simple, el que se usa en AJAX, y un modo de acceder también a servicios web como geonames, del que hablamos en el primer tema. Lo hacemos en el siguiente ejemplo: json-geonames.html, del que extraemos el código en JS (fichero geonames_call.js)

// this function will be called by our JSON callback // the parameter jData will contain an array with geonames objects function getLocation(jData) { if (jData == null) { // There was a problem parsing search results return; } var html = ''; var geonames = jData.geonames; for (i=0;i< geonames.length;i++) { var name = geonames[i]; // we create a simple html list with the geonames objects // the link will call the center() javascript method with lat/lng as parameter html = name.name+ " Latitud: " + name.lat +', longitud: ' + name.lng; } document.getElementById('resultDiv').innerHTML = html; } // calls the geonames JSON webservice with the search term function search() { request = 'http://ws.geonames.org/searchJSON?country=ES&q=' + encodeURIComponent(document.getElementById('q').value) + '&maxRows=10&callback=getLocation'; // Create a new script object // Implementación en jsr_class.js aObj = new JSONscriptRequest(request); // Build the script tag aObj.buildScriptTag(); // Execute (add) the script tag aObj.addScriptTag(); }

Este ejemplo es un poco complicado, sobre todo, por el mecanismo que usa para hacer la llamada, y que está contenido en el fichero jsr_class.js, que contiene la clase JSONscriptRequest, que será la que usemos para construirla llamada. La mecánica es la siguiente: cada vez que se introduce un nombre de un pueblo o ciudad de España, se genera un evento, y se llama a la función search. Esta función hace construye una petición, dentro de la cual está incluido callback=getLocation. El truco es que esa petición genera un callback tal que cuando se recibe la respuesta se llama a la función getLocation (definida más arriba).

Esa función recibe como variable en jData el resultado que, al estar en formato JSON, ya está, de hecho, en formato de un objeto en JS. Por eso, en el bucle for recorremos directamente el array geonames, que contiene objetos cuyas variables de instancia son name, lat y lng, que usamos para construir el código HTML que se incluye en la página.

En particular, el ejemplo anterior es un ejemplo de JSONP, JSON with paddig, que usa la capacidad que tienen los navegadores de crear elementos sobre la marcha para crear un elemento script y usarlo. Ese elemento script crea una función, que nosotros llamamos desde fuera.

Bloque 1.11

  1. Usar JSON para asignar valores a los partidos de una quiniela, con local, visitante y resultado.

1.8 El modelo de objetos #

En realidad es difícil encontrar un enfoque como el de este tema, centrado en JS como lenguaje y no como un chisme más dentro del navegador. Eventualmente, habrá que tratar con esto, así que este momento es tan bueno como cualquier otro. En realidad, la mayor diferencia entre JS-sin-navegador y JS-con-navegador es el bagaje de objetos con el que tiene que trabajar y también el modelo que se va a usar para entrada y salida: la propia página en la que está inserto el programa.

En general, lo que hace un navegador es analizar el HTML que le envía el servidor y convertirlo en un árbol, el DOM o document object model. Todo lo que hay en el documento es una hoja o un nudo dentro de ese árbol. Lo importante es que los programas JS, aparte de ser hojas dentro de ese árbol, también actúan sobre ese árbol, añadiendo o quitando hojas, o simplemente alterando sus propiedades. El DOM está definido como un estándar del W3, pero eso no quita que haya problemas de compatibilidad entre los diferentes navegadores. Por ejemplo, Mozilla tiene su modelo de objetos, que usa en sus navegadores, los más bonitos del mundo mundial.

Si a estas alturas no te has instalado Firefox u otro navegador libre, ya tardas. Firefox es más respetuoso de los estándares, y también más rápido, sobre todo las últimas versiones.

Para empezar, vamos a ver qué pinta tiene el DOM de un documento cualquiera. Por ejemplo, usemos la página de la asignatura. En Firefox, hay que instalar el DOM Inspector, un plugin, tras lo cual se ve el DOM completo con la combinación de teclas Ctrl+Shift+I; para la versión 3 de Firefox, es mejor que te instales Firebug. Para ésta página, saldría algo así: DOM de una página La estructura es la que cabe esperar: hay un nodo raíz, etiquetado como document, del que descienden las dos partes del documento HTML: HEAD y BODY. Y de ahí, pues el resto.

Todos las herramientas relacionadas con la página web, como las CSS, trabajan y tienen en cuenta esta estructura DOM del documento. Y cuando un programa JS se ejecuta dentro de un documento, puede alterar su estructura. Veámoslo, por ejemplo, en el docwrite.html:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Prueba document.write</title> </head> <body> <h1>Prueba document.write</h1> <script type='application/javascript'>document.write('<p>Esto es un nuevo nodo</p>')</script> <hr> <address><a href="mailto:jmerelo@localhost.localdomain">Juan J. Merelo</a></address> <!-- Created: Wed Feb 21 18:45:35 CET 2007 --> <!-- hhmts start --> Last modified: Wed Feb 21 18:46:41 CET 2007 <!-- hhmts end --> </body> </html>

que crearía un DOM con el aspecto siguiente: Creación de un nodo en el DOM
	con JS Como se ve, el nodo P sigue al nodo script, es decir, que el nodo se crea en el DOM justamente siguiendo al nodo que incluye el script. En realidad, se crean tantos nodos como a uno le interese, pero de esta forma sólo se pueden colocar en el sitio donde está el script. Así que habrá que imaginar otra forma de hacerlo si queremos crear o alterar los nodos en cualquier otro sitio.

Navegar por el modelo de objetos no es excesivamente complicado, gracias a las funciones getElementsBy*, que escogen elementos del DOM de acuerdo con alguna característica. getElementsByTagName escoge elementos por el nombre de la etiqueta, mientras que getElementById usa el atributo id; nótese la diferencia entre Elements y Element; en el primer caso se devuelve un Array y en el segundo uno solo, porque la Id es única. Cuando se trabaja con un documento, habrá que usar de forma sabia los atributos y clases de forma que sea fácil escoger y trabajar con los elementos de un tipo determinado, o con un elemento en concreto. Por ejemplo, getElementByTagName('h2') devolverá un array con todos los elementos que tengan esa etiqueta, y getElementById('ej.t1.3') devolverá el tercer bloque de ejercicios de este tema, como veremos en el ejemplo siguiente (que está incluido en la misma página):

Bloque
 
function putBloque(value) { var ejs = document.getElementById('ej.T1.'+value); document.getElementById('resultado1').innerHTML=ejs.textContent; }

Un par de líneas sólo de JS: una para buscar el elemento (la primera) y la segunda para extraer su contenido (textContent) e introducirlo en otro, el elemento resultado1 que teníamos preparado al efecto. innerHTML es el HTML interno de un elemento: al asignarle un valor, efectivamente, sustituimos parte del contenido de la página dinámicamente. ¿No es una maravilla?

Bloque 1.12

  1. Salvar esta página, y sobre ella hacer un programa JS que imprima todos los títulos de secciones.

1.9 Usando GreaseMonkey #

No se sabe porqué los temas de JS tienen tanta relación con los primates, pero el hecho es que GreaseMonkey es un plugin para los navegadores Mozilla que permite instalar en el navegador programillas JS específicos de una página o grupo de páginas. Para trabajar con él, lo primero que hay que hacer es instalarlo y reiniciar el navegador.

Una vez hecho eso, GreaseMonkey reconoce los scripts con la extensión .user.js como propios, los instala, y permite gestionarlos, activarlos, y desactivarlos, desde un icono con un monito en la barra inferior del navegador. O sea, que una vez que se vea el monito, podemos cargar este programa (aap-nav.user.js) que lo usa:

// ==UserScript== // @name AAP-Nav // @namespace http://geneura.org/projects/greasemonkey // @description Navegación por las secciones de AAP // @include http://geneura.ugr.es/~jmerelo/asignaturas/* // ==/UserScript== GM_log('Entrando AAP-Nav'); var h2 = document.getElementsByTagName('h2'); var a_nodes = new Array; var anchors = new Array; for ( var secs = 0; secs < h2.length; secs ++ ) { var thisA = h2[secs].getElementsByTagName('a'); a_nodes[secs] = thisA[0]; anchors[secs] = thisA[0].getAttribute('name'); GM_log('Anchor ' + secs + " " + anchors[secs]); } for ( var secs = 0; secs < h2.length; secs ++ ) { var span = document.createElement('span'); span.setAttribute('style','background:lightblue'); if ( secs > 0 ) { var ahref = document.createElement('a'); ahref.setAttribute('href','#'+anchors[secs-1]); var txt=document.createTextNode('^'); ahref.appendChild(txt); span.appendChild(ahref); } if ( secs < h2.length -1 ) { span.appendChild(document.createTextNode(' | ')); var ahref = document.createElement('a'); ahref.setAttribute('href','#'+anchors[secs+1]); var txt=document.createTextNode('v'); ahref.appendChild(txt); span.appendChild(ahref); } a_nodes[secs].parentNode.insertBefore(span,a_nodes[secs]); }

Este programa añade unas flechitas de navegación a los apuntes de esta asignatura, de forma que se pueda pasar de cada sección a la anterior a la siguiente (de ahí lo de aap-nav). Tiene dos partes: la primera parte halla las etiquetas de navegación, y la segunda las inserta. Tres partes, de hecho, si incluimos las declaraciones del principio, que son para uso y disfrute del propio GreaseMonkey. Las dos primeras son terminológicas: cómo se llama, y qué espacio de nombres usa. Ésto es para distinguir scripts con el mismo nombre de diferentes fuentes. La tercera, una descripción, aparece en los directorios correspondientes y cuando gestionamos los scripts.

El cuarto apartado es el más importante. Es un patrón de las páginas en las que va a funcionar el script. Éste no tendría sentido fuera de las páginas de las asignaturas que imparto, así que incluimos en el mismo simplementa a las que hay en ese directorio. Cuando el navegador cargue alguna página con ese patrón, GM lo detectará y cargará el script.

El programa en sí hace uso de los elementos explicados en la sección anterior: extrae del documento los títulos de capítulo (h2) y de ahí los anchor (a name) y sus atributos, metiendo todo lo metible en un bucle. Hace falta tenerlos todos en un array, por eso se usa un segundo bucle para insertar en la página los elementos de navegación.

Este segundo bucle crea elementos a tutiplén, usando createElement (para crear un elemento), setAttribute (para poner su atributo) y createTextNode (para meter texto dentro de los elementos). Luego, a modo de injerto, se van metiendo los elementos unos dentro de otros usando appendChild. Y, finalmente, se insertan los elementos creados en el documento en la penúltima línea:

a_nodes[secs].parentNode.insertBefore(span,a_nodes[secs]);

que navega desde el ancla hasta su padre (parentNode) e inserta antes del mismo (insertBefore) el span que hemos creado previamente. El resultado, si todo ha ido bien, deberías verlo en este mismo tutorial.

Además, hace uso de algunas funciones propias de GM: GM_log, que escribe en la consola de Javascript. Muy útil para depurarlo, inútil en producción; pero si abres la consola de JS verás los mensajes que ha usado.

Bloque 1.13

  1. Hacer un script JS que navegue desde cada bloque de ejercicios al siguiente.

1.10 Para finalizar #

Cualquiera de los recursos que listo ahí abajo pueden resultar útiles para ampliar información sobre JavaScript. Quizás también pueda ser interesante usar alguna librería que facilite su uso como Mochikit o Prototype. También el Google Web Toolkit permite desarrollar en AJAX programando sólo en Java, aunque pueda que el JS generado necesite algún retoque adicional.

En el tag Javascript de del.icio.us puedes encontrar muchos más enlaces sobre el mismo; también en el mío propio. Entre ellos está esta colección de características ocultas de Javascript, que nos ha sorprendido a más de uno

.

1.11 Agradecimientos #

Agradezco a los comentaristas de los diferentes anuncios que publiqué en Barrapunto sus comentarios y sugerencias. También a Javier Espigares por la lectura y comentarios sobre las versiones previas de este tema y el anterior.

1.12 Bibliografía #

Hay dos libros fundamentales para aprender JS, aunque están muy enfocados a JS en el navegador: Javascript: The Definitive Guide, el libro del rinoceronte, editado por O'Reilly, que está disponible como recurso eletrónico en la UGR y JavaScript Bible, de Danny Goodman, un tocho considerable, en el que hay de todo, y que viene con un útil CD con ejemplos. También está en la Biblioteca de la UGR. Y, por si fuera poco, una referencia de bolsillo de JS y un libro llamado JS profesional projects. Es que en la biblioteca de la UGR hay de todo.

Como recursos adicionales, las páginas de Javascript en Mozilla.org, el estándar completo, y el curso de Javascript de Víctor Rivas Santos.

Por último, Javascript: The good parts es un manual bastante completo que menciona muchos trucos para trabajar con este lenguaje.


Este material está disponible bajo los términos de la licencia GNU FDL.
Valid HTML 4.01 Transitional
Juan J. Merelo