Comportamiento con JavaScript

JavaScript es un lenguaje interpretado por el navegador web que permite al autor manipular dinámicamente el documento, sus estilos y la ventana misma del navegador, para hacer la experiencia del lector más natural y amena. JavaScript fue creado en 1995 por Brendan Eich cuando trabajaba para Netscape. Un año después Microsoft produjo su propia versión llamada JScript. También en 1996, Netscape sometió JavaScript a la asociación Ecma International para consideración como estándar de la industria, el resultado fue ECMAScript.

ECMAScript es un estándar internacional de un lenguaje genérico de "scripting" para extender la funcionalidad de un programa cualquiera, no sólo navegadores web. Hay un número creciente de implementaciones basadas en ECMAScript que además extienden su funcionalidad, como el inicial JavaScript de Netscape, JScript de Microsoft, ActionScript de Macromedia (adquirida por Adobe), SpiderMonkey y Rhino de Mozilla, etc. Sin embargo, el nombre ECMAScript no tomó popularidad, y cuando la mayoría de la gente dice "JavaScript" está haciendo referencia al lenguaje en forma general, no a la implementación de Netscape, y así se hará en este documento.

Cualquier software que quiera permitir al usuario automatizar tareas propias, en lugar de crear un nuevo lenguaje de scripting, puede echar mano de JavaScript. De esta forma, JavaScript es por naturaleza un lenguaje genérico. Un desarrollador que conozca este lenguaje, puede aprovechar su conocimiento para automatizar variedad de aplicaciones; como ocurre en la actualidad en programación de dispositivos móviles, acceso a bases de datos orientadas a documentos, animación digital y otros. Pero su uso más difundido ha sido históricamente la web, en la programación en el lado del cliente y más recientemente en el servidor web. En este material se presentará este lenguaje precísamente ligado al navegador web.

Generalidades

JavaScript es un lenguaje similar a C/C++/Java. Es sensitivo a mayúsculas y minúsculas, por lo que resulta más consistente con XHTML que con HTML. Aunque no es obligatorio, cada sentencia en JavaScript debe terminar en punto y coma, y se considera una mala práctica omitirlos. Los comentarios utilizan la notación de C++:

// comentario hasta el final de línea
/* comentario que puede
   extenderse varias líneas */

El código JavaScript puede aparecer en cuatro lugares relacionados con el documento web: en el elemento script, en un archivo .js externo, en un evento intrínseco, y con el pseudoprotocolo javascript:.

Código JavaScript en el elemento script

Se puede escribir código JavaScript en el contenido del elemento script, el cual debe ser hijo directo de head o body. El siguiente ejemplo muestra los cuadrados de los primeros 20 números naturales:


   

Cuadrados naturales

]]>
Un elemento script en el cuerpo del documento. Correr este ejemplo

Mientras el navegador está cargando un documento web, va mostrando sus elementos (títulos, párrafos, imágenes, tablas, etc.), a medida que los encuentra. Lo mismo pasa con los scripts. Inmediatamente que el navegador encuentra un elemento script, ejecuta su código.

En el ejemplo del js_square_list_1, se invoca al método write() del objeto document que representa al documento ante JavaScript. El método document.write() recibe una lista de valores separados por coma y su salida es insertada como código HTML inmediatamente después del elemento script que lo invoca, es decir, su invocación altera la estructura del documento. Una vez que el script ha terminado su ejecución, el navegador continúa procesando el resto del documento como de costumbre, y procesará tanto el código provisto por el autor en el documento original como el código insertado con document.write() tras ejecutar el script.

Existen diferencias si el código JavaScript está en un documento HTML o XHTML. En XHTML el objeto document no dispone de un método write(), ya que XML prohíbe modificar el documento mientras éste se esté cargando [W3C]. Incluso en HTML, el uso de document.write() se considera ahora una pobre práctica de programación, sin embargo se utilizará en la primera parte de este capítulo por ser fácil de comprender. Si se incrusta código JavaScript en un documento XHTML que tiene caracteres especiales como menor que (<), mayor que (>), o ampersand (&), se debe encerrar en una sección CDATA:


]]>

Como se puede deducir del js_square_list_1, JavaScript interpreta el texto entre apóstrofes ('') y entre comillas ("") como cadenas de caracteres. Las variables no se declaran precedidas por su tipo de datos, sino por la palabra reservada var. Y el ciclo for tiene la misma sintaxis de C.

5 pts.

En un documento HTML5, modifique el ejemplo del js_square_list_1 para desplegar en una tabla HTML, las tablas de multiplicar del 1 al 20. La tabla debe tener una fila de encabezado, una columna de encabezado y 20x20 = 400 celdas de datos, como se ilustra a continuación. Asegúrese de que su documento sea válido ante los estándares.

*123...20
1123...40
2246...60
3369...80
..................
20406080...400
5 pts.

En un documento HTML5 escriba una tabla con las primeras 10 potencias naturales de los números 1 a 20. La tabla tendrá una fila de encabezado, una columna de encabezado y 20x10 celdas de datos. Indague sobre el método Math.pow(). Recuerde validar su documento. [Opcional: si gusta puede agregar su tabla de potencias al documento del ejercicio anterior].

El elemento script tiene cinco atributos opcionales: type, src, charset, defer y async. El atributo type indica el lenguaje de programación utilizado por el script. Hasta la fecha sólo existe un lenguaje reconocido: JavaScript, y es el considerado por defecto en HTML5, por lo que el programador puede omitirlo, como se hará en el resto de este capítulo. Los demás atributos se estudiarán en la próxima sección.

Código JavaScript en un archivo externo

El elemento script permite escribir código JavaScript en su contenido, pero el Consorcio Web recomienda sacar el comportamiento del documento web a un recurso reutilizable, por convención, un archivo con extensión .js. Este es el segundo lugar donde se puede escribir código JavaScript. El mismo elemento script permite hacer la inclusión del archivo externo con el atributo src. El ejemplo del js_square_list_2_xhtml tiene el mismo efecto que el ejemplo del js_square_list_1. Es importante resaltar que un script debe tener contenido o un recurso externo en el atributo src, pero no ambos, de lo contrario generará un documento inválido en (X)HTML5.


   

Cuadrados naturales 2

]]>
El código JavaScript puede estar en un recurso externo y ser importado con el atributo src del elemento script. Correr este ejemplo.
\n');
for ( var n = 1; n <= 20; ++n )
   document.write('
  • ', n, '2 = ', n * n, '
  • \n'); document.write('\n'); ]]>
    El archivo JavaScript squares_list2.js referido en el ejemplo anterior.
    5 pts.

    En los ejercicios sobre las tablas de multiplicar y las tablas de potencias, extraiga el código JavaScript que escribió a uno o varios archivos .js externos. Asocie el archivo .js en su documento (X)HTML. Asegúrese de que ambas tablas sean desplegadas correctamente en un lugar adecuado del documento.

    En HTML la etiqueta de cierre </script> es siempre requerida, aunque el contenido del script sea vacío; es decir, siempre se deben escribir de la forma <script src="archivo.js"></script>. En XHTML se puede tener una etiqueta vacía de la forma <script src="archivo.js"/>.

    Cuando el código de un script se encuentra en un archivo externo, el atributo charset permite indicar la codificación empleada del archivo .js si ésta difiere de la codificación del documento (X)HTML. Los atributos defer y asinc permiten controlar en qué momento se debe ejecutar el script, como se indica en la js_script_defer_async.

    defer async Comportamiento Ejemplo
    Si se omiten ambos atributos, el script es ejecutado inmediatamente; y antes de continuar procesando (parsing) el resto del documento. Dado que el código JavaScript está en un recurso externo, el navegador debe esperar a que éste se haya descargado completamente antes de continuar desplegando el documento.
    ]]>
    defer El script es ejecutado inmediatamente después de que el documento y sus requisitos (imágenes, hojas de estilo, etc.) se hayan terminado de cargar por completo. Este es el momento idóneo para ejecutar código JavaScript en la mayoría de casos.
    ]]>
    async Indica al navegador continuar cargando el documento y solicitar al servidor web el archivo .js, y cuando lo reciba, ejecutarlo simultáneamente con el cargado del documento. Sólo está disponible en HTML5 y no es soportado por Internet Explorer.
    ]]>
    defer async Carece de sentido. El navegador actuará como si sólo se hubiese especificado async.
    Los atributos defer y async permiten controlar el momento en que el navegador ejecuta el script.

    Los atributos defer y async se pueden especificar únicamente cuando el script externo no haga uso de document.write(); ya que una invocación a este método obliga al navegador detener el cargado del documento, esperar a que el script termine de generar contenido, analizarlo (parse) y luego continuar con el resto del documento.

    Código JavaScript en los eventos intrínsecos

    El tercer lugar donde se puede especificar código JavaScript es en los eventos intrínsecos de ciertos elementos, tales como onload, onmouseover y onclick, como se ilustra en el js_intrinsic_events_ex1. Este código JavaScript es ejecutado únicamente si el evento es accionado. Los eventos intrínsecos se estudiarán más adelante en este capítulo.

    
       

    Eventos en JavaScript

    ]]>
    El autor puede proveer código JavaScript se que ejecuta cuando un evento ha ocurrido, como presionar un botón en este caso. Correr este ejemplo.

    Código JavaScript en el pseudoprotocolo javascript:

    El cuarto lugar donde se puede ejecutar código JavaScript es en la barra de direcciones del navegador, en el destino de un enlace o cualquier otro lugar donde se pueda escribir un URL; con el pseudoprotocolo javascript:, que es seguido por una o varias instrucciones JavaScript separadas por punto y coma. El resultado de estas instrucciones, si lo hay, se toma como un string, y es desplegado en la ventana del navegador. Ejemplos:

    javascript:5%2
    javascript:x = 3; (x < 5) ? 'x is less' : 'x is greater'
    javascript:d = new Date(); typeof d
    javascript:for(i=0,j=1,k=0,fib=1; i>5; i++,fib=j+k,k=j,j=fib) alert(fib);
    javascript:s=''; for(i in navigator) s+=i+':'+navigator[i]+'\n'; alert(s);
    

    El pseudoprotocolo javscript: ha caído en desuso, y algunos navegadores como Chrome y Safari los ejecutan pero no reemplazan el documento actual por el resultado de la expresión, lo que produce la sensación de ser ignorados.

    Tipos de datos y variables

    JavaScript define siete tipos de datos: booleanos, números, cadenas de caracteres (strings), funciones, objetos, arreglos, y valores especiales. En las siguientes secciones se explorará cada uno de ellos.

    Números

    JavaScript no hace diferencia entre números enteros y de punto flotante. Todos son representados internamente como punto flotante de 64bits (IEEE 754). Las constantes literales numéricas de JavaScript siguen las mismas convenciones de C, excepto los números octales que no son parte de ECMAScript. La js_numeric_literals muestra algunos ejemplos de constantes literales numéricas.

    Tipo de constante literal Ejemplos
    Enteros en base 10 0, 16777216, -3
    Enteros hexadecimales 0xFEE0, -0xff
    Notación fija -3.141592, .1
    Notación exponencial 6.67e-11, .1E3
    Constantes literales numéricas en JavaScript

    Cuando un valor flotante llega a ser más grande que el más grande de los representables, se almacena con el valor especial Infinity o su opuesto negativo. Cuando se hace una operación indefinida, se genera un número especial NaN (not-a-number), el cual nunca es igual a nada, incluso ni a él mismo, por eso debe usarse la función especial isNaN(). Otra función práctica es isFinite(), que prueba si un número no es Infinite y no es NaN. A continuación una lista de constantes numéricas especiales:

    • Infinity. Valor especial usado en lugar de un número gigante que no cabe en un double de 64 bits, por ejemplo el resultado de la expresión 17/0.
    • NaN. Acrónimo de "not-a-number". Es un valor especial usado cuando una expresión no genera un número válido, como la división 0/0 o tratar de convertir un string no apto a un número como parseInt("cinco").
    • Number.MAX_VALUE. El número más grande representable en un double de 64 bits.
    • Number.MIN_VALUE. El número decimal más pequeño (cercano a cero) representable en un double de 64 bits.
    • Number.NaN. Igual a NaN.
    • Number.POSITIVE_INFINITY. Igual a +Infinity.
    • Number.NEGATIVE_INFINITY. Igual a -Infinity.
    5 pts.

    Escriba un script que genere una tabla con las siguientes columnas: el nombre de la constante, el valor real de dicha constante, el valor que precede a la constante, el valor sucesor de la constante, el resultado de invocar isNaN() con la constante, y el resultado de invocar isFinite() con la constante. En una columna encaebezado escriba cada una de las constantes listadas anteriormente. Llene las celdas de datos de la tabla con el resultado de evaluar cada expresión o función en la columna con la constante de la fila. La tabla tendrá una estructura similar a la siguiente.

    ConstanteValorPredecesorSucesorisNaN()isFinite()
    InfinityInfinityInfinityInfinityfalsefalse
    NaN...............
    Number.MAX_VALUE...............
    ..................
    Number.NEGATIVE_INFINITY-Infinity-Infinity-Infinityfalsefalse
    5 pts.

    Agregue dos filas más a la tabla del ejercicio anterior. La primera con una expresión aritmética que genera un valor real, y la segunda que genere un valor indefinido.

    Cadenas de caracteres (strings)

    Las cadenas literales en JavaScript se encierran entre comillas dobles o simples y se pueden anidar estos separadores dentro de la cadena. Ya que es común escribir código HTML dentro de JavaScript y viceversa, es conveniente que el desarrollador adquiera el hábito de uniformar las comillas para cada cual. El siguiente ejemplo utiliza comillas dobles para valores de atributos de (X)HTML y comillas simples para strings en JavaScript:

    Thanks]]>

    Igual que en C/C++, JavaScript emplea secuencias de escape iniciadas en backslash (\) para anular el efecto de un carácter especial del lenguaje de progamación, tales como \n para el cambio de línea, \r para el retorno de carro, \" para comillas dobles, \' para apóstrofe o comilla simple, \0 para el carácter nulo y \\ para la barra invertida (backslash). Se puede utilizar el backslash frente a un cambio de línea para continuar un string en varias líneas si el intérprete de JavaScript soporta EcmaScript 5, lo cual ocurre en la mayoría de navegadores modernos. El js_newline_scape muestra una cadena multilínea.

    
      var str = 'Cada una de estas líneas\n\
        tiene dos cambios de línea\n\
        el primero es parte del string,\n\
        el segundo es ignorado por JavaScript.'
    
      str = str.replace(/\t/g, '');
      document.write('
    ', str, '
    '); ]]>
    Se puede continuar un string en varias líneas anulando el cambio de línea con un carácter de escape (\). Correr este ejemplo.

    La línea 7 del js_newline_scape emplea expresiones regulares para quitar los tabuladores que son parte del string, y el resultado es asignado al string mismo. Esta asignación debe hacerse obligatoriamente si se quiere reflejar el cambio en el string original, ya que todos los string en JavaScript son inmutables, es decir, ninguna operación puede modificar los caracteres de un string ya que son de sólo lectura.

    En JavaScript las cadenas de caracteres o strings son un tipo de datos atómico, no un arreglo de caracteres. De hecho JavaScript no tiene el concepto de carácter (char) como sí ocurre en otros lenguajes de programación. Un carácter se representa como un string de longitud 1. El método str.charAt(i) permite obtener un string con el carácter que está en la posición i de str, donde el índice i está basado en cero, es decir, 0 representa el primer carácter de la cadena. El siguiente código muestra varias operaciones con strings:

    var visitorName = 'Chema';
    var str = 'Bienvenido ' + visitorName;       // concatenación de textos
    var len = str.length;                        // longitud de cadena
    var lastChar = str.charAt(str.length - 1);   // obtener un carácter de la cadena
    var sub = str.substring(6, 4);               // obtiene 'nido'
    var i = str.indexOf('e');                    // la posición de la primera letra 'e' en str (2)
    var dbg = 'i = ' + i;                        // concatenación con conversión, genera 'i = 2'
    

    El operador de suma (+) cuando alguno de sus operandos es una cadena, hace concatenación. En ECMAScript 5, el operador [] permite acceder a un carácter específico del string de la misma forma que el método str.charAt(). Debe tenerse claro que ambos retornan un nuevo string con el carácter solicitado, por lo que el original no se puede modificar.

    15 pts.

    Escriba un poema de su agrado (o cualquier otro texto con varios párrafos) en una variable string de JavaScript. Separe cada verso por un cambio de línea ('\n') y cada estrofa por dos cambios de línea ('\n\n').

    Imprima el poema dos veces. La primera vez en orden natural, reemplazando los cambios de línea por elementos br y las estrofas por párrafos (p).

    La segunda vez imprima las estrofas (no las líneas que lo componen) en orden inverso en el documento. Para hacer notorio al lector que las estrofas están en orden, o en orden inverso, imprima el número de la estrofa dentro de un elemento span. Si gusta puede escribir el texto de la estrofa dentro de otro elemento span, ambos dentro del párrafo p. Con estilos CSS haga a este número visiblemente grande y ubicado a la izquierda o derecha de la estrofa y deseablemente detrás del resto del texto (propiedad z-index) si hay intersección entre ambos. La siguiente imagen muestra una captura de pantalla de un ejemplo de poema invertido.

    Conversiones entre números y strings

    Cuando un número aparece en un contexto donde se requiere un string, JavaScript lo convierte automáticamente. Por ejemplo, si uno de los operandos de + es una cadena y el otro es un número, el operador + actuará como el operador de concatenación y convertirá el número en una cadena, como se hace en la línea 3 del js_number_to_string_operator_plus.

    ', text, '

    \n');]]>
    El operador de concatenación convierte números a cadenas automáticamente. Correr este ejemplo.

    Las conversiones explícitas se pueden hacer con el constructor String(numero), y varios métodos de la clase Number: numero.toString(base), numero.toFixed(decimales), numero.toExponential(decimales) y numero.toPrecision(decimales). Ejemplos:

    Conversión explícita de números a string. Correr este ejemplo.
    5 pts.

    Pruebe todas las expresiones que aparecen en el js_number_to_string_operator_plus y js_number_to_string_explicit en el intérprete de JavaScript de su navegador. Por ejemplo, Chrome provee una Consola de JavaScript (Ctrl+Shift+J ó Cmd+Opt+J), donde puede introducir sentencias que serán evaluadas "en vivo". Sugerencia cognitiva: escriba cada expresión manualmente, en lugar de copiarla y pegarla. Al final, copie los resultados que haya obtenido en la consola en un archivo de texto.

    Esta consola además reporta errores gramaticales o de ejecución en el código fuente, por lo cual es importante revisarla constantemente mientras se está desarrollando en JavaScript. Mantenga el hábito de probar cada ejemplo de este capítulo en dicha consola.

    En la dirección opuesta, cuando un string se utiliza en un contexto donde se requiere un número, será traducido automáticamente por JavaScript, por ejemplo:

    var product = "21" * "2";  // product == 42.
    

    Con la notación anterior no se podrá sumar un string a un número con el operador +, ya que será interpretado como concatenación. El constructor Number(str) convierte el string str en un número, siempre que str tenga formato de número en base 10 y no inicie con espacios en blanco. Las funciones globales parseInt(str,base) y parseFloat(str,base) son más flexibles, y asumen que str está en base base (10, si se omite) y lo convierten en un número entero o real respectivamente. Ejemplos:

    Conversión explícita de string a números

    Si la cadena a convertir inicia con "0x" se interpreta que está en hexadecimal. Si inicia con 0 su resultado es indefinido, ya que algunas implementaciones podría interpretar octal o decimal, por lo que es conveniente siempre especificar la base como segundo parámetro de parseInt(). Si no se puede convertir a un número, estas funciones retornan NaN.

    Booleanos

    Los valores booleanos sólo pueden contener los valores literales false o true. Cuando se usa un booleano en un contexto numérico, se convierten automáticamente a los valores respectivos 0 y 1. Si se usan en un contexto string, JavaScript los convierte implícitamente a las cadenas "false" y "true". Los valores especiales NaN, null, undefined y la cadena vacía ("") siempre se convierten a false; todos los demás a true (como Infinity). Para hacer explícita la conversión, es recomendable emplear la función constructora Boolean():

    var x_as_boolean = Boolean(x);
    
    5 pts.

    ¿Qué valor y tipo de datos generan las siguientes expresiones en JavaScript? Escriba sus repuestas en un documento de texto o una tabla HTML (puede tomar el código fuente de la que está abajo). Después de responder la columna "Predicción", utilice la consola de JavaScript de su navegador para evaluar cada expresión y anotar en la columna "Resultado" la respuesta que obtuvo. ¿Qué porcentaje de acierto obtuvo?.

    ExpresiónPredicciónResultado
    1var a = 0/0 == NaN;
    2var b = a == false;
    3var c = Number(a);
    4var d = Number(b);
    5var e = String(a);
    6var f = String(b);
    7var g = Boolean('true');
    8var h = Boolean(' True ');
    9var i = Boolean('false');
    10var j = Boolean('0');
    11var k = Boolean(0);
    12var l = Boolean(-1);
    13var m = Boolean(null);
    14var n = Boolean('');
    15var o = Boolean(' ');

    Funciones

    El programador puede declarar sus propias funciones con la palabra reservada function, el nombre opcional de la función, los parámetros sin tipos entre paréntesis ( ), y el cuerpo de la función entre llaves { }. Las funciones en JavaScript son un tipo de datos más, por ende, una función es un valor; así, las funciones se pueden almacenar en variables, miembros de objetos, arreglos, y pasarse por parámetros en una forma más natural que en C/C++. Cuando una función se asigna a un objeto como un miembro, recibe el nombre especial de método. En caso de asignarse a una variable, puede omitirse el nombre de la función, lo que en JavaScript se llama función literal o función lambda en homenaje al lenguaje Lisp que fue uno de los primeros en permitir funciones sin nombre:

    Tres formas de declarar una función en JavaScript

    Una tercera forma de definir una función es pasar sus argumentos y cuerpo como strings a la función constructora Function(), lo cual es poco usado e incluso, menos eficiente. Cuando el valor de una variable es una función, se puede invocar ésta usando el operador paréntesis () tras el nombre de la variable, como se hizo en la línea 11 del js_function_declaration_types con square2.

    5 pts.

    Programe la función factorial recursivamente. Imprima en una lista no ordenada de (X)HTML el factorial de los primeros 20 naturales. ¿Qué sucede si llama su función con un número muy grande o con parámetro no numérico?

    Objetos

    Un objeto es una colección de valores nombrados, que usualmente se les refiere como propiedades, campos o miembros del objeto, y se les accede utilizando el operador punto. Por ejemplo:

    image.width
    image.height
    document.myform.button
    document.write("write es un método: una propiedad cuyo tipo de datos es una función");
    

    El operador punto permite acceder a las propiedades utilizando identificadores. Pero JavaScript permite también usar cadenas de caracteres para acceder a las propiedades, con el operador corchetes []. Esta segunda notación es mucho más flexible ya las cadenas de caracteres pueden ser el resultado de evaluar una expresión. De esta forma, el operador corchetes permite emplear los objetos de JavaScript como arreglos asociativos, también llamados mapas (maps) o tablas de dispersion (hash). Ejemplos:

    image["width"]
    image["height"]
    document["myform"]["button"]
    document["write"]("write es un método: una propiedad cuyo tipo de datos es una función");
    

    Los objetos se crean llamando funciones constructoras con el operador new, después de lo cual se usan como de costumbre. Estos objetos son almacenados en memoria dinámica por el navegador, el cual incorpora un recolector de basura (garbage collector), de tal forma que ahorra al programador la responsabilidad de liberar la memoria de cada objeto creado.

    Los objetos se crean con el operador new y funciones constructoras

    En la línea 4 del js_new_operator el objeto point se creó como un objeto vacío, y sus propiedades se fueron agregando luego con el operador de asignación (líneas 5 y 6). Existe una notación para definir objetos literales, con o sin propiedades, útil para inicializaciones:

    Notación para escribir objetos literales en JavaScript

    Las llaves {} en una sección de declaración de variables (var) de JavaScript indican la creación de un objeto literal. Los nombres de las propiedades pueden declararse como identificadores (como se hizo con property1 en js_literal_object_notation) o como strings (como se hizo con "property2" en js_literal_object_notation). Los valores de cada propiedad pueden ser de cualquier tipo de datos de JavaScript (booleano, numérico, string, función, arreglo, u objeto), sea como valores literales o como resultado de una expresión aritmética. Cada propiedad debe separarse por una coma, y no por punto y coma. Las declaraciones de objetos literales se pueden anidar como se hizo con rectangle en el js_json_ex1.

    Notación de objetos de JavaScript

    La notación presentada en el js_json_ex1 para escribir objetos literales, es importante, y recibe el nombre de JavaScript Object Notation (JSON). Es ampliamente usada para representar datos y funciones que deben ser intercambiados entre el servidor web y el navegador, en bases de datos orientadas a documentos, y un número creciente de tecnologías. Más adelante se completará esta notación para representar arreglos.

    Cada vez que JavaScript evalúa un objeto literal, se crea un objeto diferente, debido a que los valores de las propiedades pueden ser expresiones que cambian de una evaluación a otra, por ejemplo, en un ciclo. El js_literal_objects muestra un programa JavaScript que construye una tabla (X)HTML con una lista de personas ficticias generadas aleatoriamente a partir de un arreglo de nombres y apellidos. El programa consta de dos declaraciones de variables globales, cuatro declaraciones de funciones y una invocación de función (línea 37). Al ser invocada, la función imprimirPersonas() recibe el número deseado de personas y por cada una de ellas invoca a crearPersona(), la cual crea un objeto literal (líneas 13 a 17), lo almacena en una variable local, y luego retorna una referencia a dicho objeto. La función imprimirPersonas() recibe el objeto creado (línea 33) y lo pasa por parámetro a imprimirPersona() quien genera la línea correspondiente en la tabla. Es importante notar que en cada iteración del ciclo la evaluación del objeto literal (líneas 13 a 17) genera una persona distinta, y no el mismo objeto, incluso aunque el valor de sus propiedades no cambiaran.

    ', numero, '',persona.nombre,
          '', persona.edad, '', persona.ingreso, '');
    }
    
    // Crea e imprime en una tabla (X)HTML la cantidad solicitada de personas aleatorias
    function imprimirPersonas(cantidad)
    {
       document.write('');
       for ( var i = 1; i <= cantidad; ++i )
          imprimirPersona( crearPersona(), i );
       document.write('
    '); } imprimirPersonas(300); ]]>
    Cada vez que se evalúa un objeto literal, JavaScript crea un nuevo objeto. Correr ejemplo completo.

    Si un objeto se emplea en un contexto booleano, se traduce a true si el objeto no es null. Si un objeto aparece en un contexto string se invocará su método toString() para hacer la conversión. Si un objeto aparece en un contexto numérico, JavaScript invocará valueOf() para hacer la conversión. El desarrollador puede sobrescribir estos métodos en sus propios objetos, lo cual es una práctica recomendada de programación.

    10 pts.

    Escriba un objeto Círculo, Triángulo y Rectángulo. En cada uno de ellos almacene su posición y dimensiones. Escoja valores arbitrarios para cada uno de ellos. Provea dos métodos en cada objeto, uno para calcular el perímetro y otro para el área. Haga un programa en JavaScript que imprima para cada una de las tres figuras: su tipo, posición, dimensiones, perímetro y área. Para este ejercicio no tiene que dibujar las figuras geométricas, sino imprimir sus coordenadas y resultados numéricos en una tabla como la de abajo.

    #FiguraPosición/DimensionesPerímetroÁrea
    1Círculocentro(..., ...) radio(...)......
    2Triánguloa(..., ...) b(..., ...) c(..., ...)......
    3Rectánguloa(..., ...) b(..., ...)......

    Nota: si A, B y C son los vértices de un triángulo, su área y perímetro se pueden obtener mediante:

    $$ \mathit{area}=\left|\frac{A_x\left(B_y-C_y\right)+B_x\left(C_y-A_y\right)+C_x\left(A_y-B_y\right)}{2}\right| $$

    $$ \mathit{perimeter}=\sqrt{\left(A_x-B_x\right)^2+\left(A_y-B_y\right)^2}+\sqrt{\left(A_x-C_x\right)^2+\left(A_y-C_y\right)^2}+\sqrt{\left(B_x-C_x\right)^2+\left(B_y-C_y\right)^2} $$

    5 pts.

    Agregue otro triángulo a la colección de figuras hechas en el ejercicio anterior. Intente reutilizar el código de los métodos del primer triángulo en lugar de duplicarlo (consulte la sección Funciones). Haga que su nuevo triángulo aparezca en la cuarta línea de la tabla. Sugerencia: puede verificar el área de sus triángulos contra esta aplicación.

    Arreglos

    En JavaScript un arreglo es una colección de datos enumerados. Se acceden con un índice entero, basado en 0, escrito entre corchetes tras el nombre del arreglo. El siguiente ejemplo obtiene el ancho en pixeles de la segunda imagen en el documento:

    document.images[1].width
    

    Los arreglos pueden tener datos heterogéneos, de cualquiera de los 7 tipos de datos de JavaScript. Así un elemento del arreglo puede contener otro arreglo. Los arreglos se crean con el constructor Array() y sus elementos se pueden agregar simplemente asignándolos a sus índices o bien, como parámetros del constructor Array(); pero si se pasa un único entero a este constructor, de la forma Array(N) se creará un arreglo con N elementos indefinidos. La propiedad length permite acceder al número de elementos en el arreglo. Ejemplos:

    var a = new Array();                // Arreglo vacío, lo mismo que: var a = [];
    a[0] = 1.2;                         // Un elemento es insertado en la posición 0
    a[1] = "JavaScript";                // Un elemento es insertado en la posición 1
    a[2] = true;
    a[4] = { x:1, y:3 };                // La posición 3 tiene un elemento con el valor undefined
    a[5] = function(x) { return x*x; }; // a[5](7) retornará 49
    a[6] = new Array();                 // Elemento a[6] almacena un arreglo vacío
    a[6][0] = -Infinity;                // Inserta un elemento en el arreglo que está en a[6]
    console.log(a.length);              // Imprime la cantidad de elementos en el arreglo: 7
    
    var b = new Array(1.2, "JavaScript", true, { x:1, y:3 });
    console.log(b.length);              // 4 elementos
    
    var c = new Array(10);              // Arrreglo de 10 elementos indefinidos
    console.log(c.length);              // Imprime 10 y no 1
    

    JavaScript permite crear arreglos literales, preferiblemente utilizados para inicialización, y son una lista de valores separados por comas dentro de corchetes, que se asignan secuencialmente empezando en 0. Los elementos también pueden ser indefinidos lo cual se logra omitiendo el valor entre comas:

    var b = [ 1.2, "JavaScript", true, { x:1, y:3 } ];
    
    var matrix = [[1,2,3], [4,5,6], [7,8,9]];           // matrix[2][1] == 8
    
    var base = 1024;
    var table = [base, base+1, base+2, base+3];
    
    var sparseArray = [1,,,,5];
    
    5 pts.

    Almacene las figuras que haya creado en los ejercicios geometric_figures_1 y geometric_figures_2 en un arreglo. Con un ciclo recorra el arreglo imprimiendo cada figura, su perímetro y su área, para generar la tabla resultado.

    30 pts.

    Escriba una función que crea y retorna un objeto figura geométrica aleatoriamente cada vez que se invoca. Llene un arreglo de 20 figuras aleatorias e imprímalas en la tabla como hizo en el ejercicio anterior. Puede apoyarse en los métodos Math.random() y Math.floor() para generar números aleatorios.

    40 pts.

    Represente el inventario de la fig_xml_inventory como una jerarquía de activos en JavaScript Object Notation (JSON). Programe métodos para imprimir cada activo e imprima el inventario en un documento HTML. Haga que cada activo genere un div en el documento, de tal forma que los div mantengan el mismo anidamiento que los objetos de JavaScript. Con estilos CSS agregue bordes y colores a los div para que sea visualmente clara la relación entre estos. La siguiente figura muestra un ejemplo potencial.

    Valores especiales

    El valor especial undefined indica que una variable u objeto fue declarado pero nunca se le ha asignado un valor, y en tal caso, JavaScript la inicializa con el valor especial undefined. Si una función no retorna un valor, JavaScript asume undefined. Si una función se invoca con menos parámetros de los esperados, éstos últimos recibirán el valor undefined. Si lo desea, el programador puede asignar explícitamente undefined a una variable.

    El valor especial null se utiliza para indicar que un objeto sí está declarado e inicializado pero ningún otro valor de su dominio aplica momentáneamente. Aunque ambos, undefined y null sirven para indicar ausencia de valor, undefined debería considerarse como una indicación de error porque un valor se esperaba y no fue provisto; mientras que null como una natural indicación de que una variable no tiene valor. Por ejemplo, si al extraer el último elemento de un arreglo con arr.pop() se obtiene undefined, el programador debe considerar que trató de extraer un elemento de un arreglo vacío; pero si arr.pop() retornó null, el programador sabrá que el arreglo tenía elementos y el último era precisamente el valor null.

    Objetos especiales

    El lenguaje de programación JavaScript provee varios objetos útiles: Date, RegExp, Boolean, Number y String. El objeto Date sirve para obtener y manipular fechas u horas, como se ilustra en el js_date_object.

     25 )
       {
          christmas.setFullYear( now.getFullYear() + 1 );
          text = 'la próxima ';
       }
    
       // Convertir la cantidad de milisegundos que faltan para navidad en dias
       var dias = (christmas.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);
       text = 'Faltan ' + dias.toFixed(0) + ' días para ' + text + 'navidad';
    }
    
    document.write('

    ', text, '

    \n'); ]]>
    Ejemplo de uso del objeto Date. Correr este ejemplo.

    El objeto especial RegExp permite manipular expresiones regulares en JavaScript, siguiendo la misma notación de Perl. [Contenido pendiente: este tema se cubrirá en el futuro].

    Los 7 tipos de datos de JavaScript pueden dividirse en dos grupos: valores primitivos (números, booleanos, strings y valores especiales (undefined y null)), los cuales no pueden tener métodos; y los valores objeto (objetos, arreglos y funciones), los cuales sí pueden tener métodos. Como se dijo, ningún valor primitivo en JavaScript tiene métodos, sin embargo, se puede saber la longitud de un arreglo o convertir un número en un string con métodos, por ejemplo:

    var longitud = 'ácido desoxirribonucleico'.length;
    var num = 777;
    var bin_str = num.toString(2);
    

    Esto es posible ya que JavaScript define tres clases correspondientes a cada uno de los tipos de datos primitivos, llamadas wrapper objects: Number, String y Boolean. Cuando a un valor primitivo se le invoca un método, JavaScript automáticamente construye un objeto temporal de la clase correspondiente y lo inicializa con el dato primitivo, invoca el método y finalmente destruye el objeto temporal. Es decir, cuando se escribe algo como

    var str = "Hola mundo!";
    var len = str.length;
    

    La propiedad length no proviene del string str, sino de un objeto temporal String inicializado con str. Nótese que el objeto String contiene una copia del texto original, y después de usarse, se desecha. Este comportamiento también aplica para los datos primitivos de Number y Boolean.

    5 pts.

    Calcule el tiempo que tarda en ejecutarse todos los scripts en conjunto, que haya programado en una página web con mucho comportamiento. Si lo desea puede descargar una copia de este material de apoyo y modificarla. Sugerencia: guarde la cantidad de milisegundos en un script en el encabezado del documento. En un script al final del documento, presente el resultado al usuario en el pie de página, en milisegundos o segundos (si se tarda más de uno). Es decir, su algoritmo debe ser capaz de ajustar la unidad automáticamente.

    Por valor o por referencia

    En JavaScript algunos datos se manipulan por valor y otros por referencia cuando se copia un dato de una variable a otra, cuando se pasa por parámetro a una función y cuando se compara. Automáticamente JavaScript sigue esta regla: los valores primitivos (booleanos, números, strings y valores especiales) son manipulados por valor; los valores objeto (objetos, arreglos y funciones), son manipulados por referencia. No existe una notación especial para que el programador pueda decidir cuáles manipular por referencia y cuales por valor. Debe ajustarse a las reglas expuestas y tener los cuidados respectivos. Por ejemplo, cuando se invoca a una función, las copias y referencias simplemente se harán de acuerdo al tipo de dato enviado:

    var n = 1;   // Es un número, el literal 1 se copia por valor a n
    var t = n;   // t será un número, copia el valor de n. Un cambio en n no afecta el valor de t
    
    // Los parametros de la funcion no tienen tipo de datos, reciben por valor o referencia
    // de acuerdo a los datos enviados en la invocacion
    function addto(total, value) { total += value; }
    
    // Se invoca con dos numeros, al llamar a addto, total y value tendrán copias por valor
    // así que la funcion no tendra el efecto que quiere conseguir, y por ende, t se mantendra
    // intalterado con su valor 1
    addto(t, n);
    
    // La comparacion se hace por valor, es decir, se comparan byte a byte variables distintas
    if ( t == n ) document.write('Los números son copiados');   // Se evaluará como true
    

    Cuando los datos se manipulan por referencia, se hace una copia de la dirección del objeto. Es decir, se copian, pasan por parámetro y comparan direcciones que apuntan a lo mismo, de forma similar a los punteros C. Por ejemplo:

    // Crea un objeto y su direccion la copia a hoy
    var hoy = new Date();
    
    // Copia la direccion de hoy a navidad, ambas variables refieren al mismo objeto
    var navidad = hoy;
    
    // Modifica al objeto apuntado, por ende, 'hoy' tambien referirá al 25 de diciembre
    navidad.setMonth(11);
    navidad.setDate(25);
    
    // Si se comparan, se hará una comparación de direcciones, y se evaluará como true
    if ( hoy == navidad ) document.write('Hoy es navidad!');
    
    // se crea otro objeto distinto pero con iguales datos
    var navidad2 = new Date(navidad.getFullYear(), 11, 25);
    
    // Esta comparacion evaluara como falsa, ya que tienen direcciones distintas
    if ( navidad == navidad2 ) document.write('La navidad es universal!');
    
    
    // Una funcion recibira por valor o referencia dependiendo de como se le invoque
    function addDays(date, days)
    {
       date.setDate( date.getDate() + days );
    }
    
    // En esta invocacion, date recibira una copia de direccion de navidad y days una copia del
    // literal 6 logrando el efecto deseado para navidad, pero 'hoy' se vera afectado tambien.
    addDays(navidad, 6);
    
    // Esta funcion no provoca el efecto deseado pero ilustra como trabajan las direcciones
    function addDaysB(dateB, daysB)
    {
       var tmpDate = new Date(dateB);
       tmpDate.setDate( tmpDate.getDate() + daysB );
       dateB = tmpDate; // Esto solo modifica la direccion de dateB y no al objeto real
    }
    
    // Al hacer esta invocacion la funcion addDaysB crea otro objeto internamente y lo asigna a su
    // parametro temporal dateB y no a navidad2. Al salir de la función, navidad2 permanece
    // inalterada. Esto también explica cómo actuar cuando no se quiere alterar datos que son pasados
    // por referencia: haciendo copias
    addDaysB(navidad2, 6);
    

    Dado a que los strings son inmutables, se comportan por valor como muestra el js_string_by_value_or_reference:

    Determinar si JavaScript maneja los string por valor o por referencia. Correr este ejemplo.

    Variables

    Una de las primeras diferencias visibles con otros lenguajes como C es que las variables de JavaScript no tienen un tipo de datos, por lo que es perfectamente válido asignarles un valor de un tipo y luego otro de diferente naturaleza:

    var i = 16;
    i = "dieciséis";
    

    Antes de que se pueda usar una variable, debe haberse declarado con la palabra reservada var, o JavaScript declarará una implícitamente; lo cual puede traer efectos secundarios indeseados. En ECMAScript 5 el autor puede incluir la cadena literal 'use strict';. Esta constante literal provoca que en adelante el intérprete sólo cree variables cuando están en una sección var y genere un error en otras circunstancias. La instrucción 'use strict'; además activa otras restricciones acordes a las prácticas sanas de programación, por lo cual se recomienda incluir esta cadena al inicio de todos sus programas JavaScript.

    En la declaración se deben inicializar las variables, sino JavaScript lo hará con el valor undefined. Si una variable se declara dos veces en secciones var distintas y la segunda declaración tiene inicialización actúa como una simple asignación. Se pueden declarar variables en cualquier lugar donde pueda haber una sentencia, y en la primera sección de la cláusula for o for/in.

    // Crea una variable i. Al terminar el ciclo i tendrá el valor 10
    for(var i = 0; i < 10; ++i) document.write(i, '\n');
    
    // No crea otra variable i, sino que reutiliza la existente
    for(var i in obj) document.write(i, '\n');
    

    Cuando se trata de leer una variable que no existe, se genera un error y el navegador detiene la ejecución del script. Si se le trata de asignar algo a una variable inexistente, se creará una variable global implícita. Las variables globales son visibles en cualquier lugar donde haya código JavaScript.

    Las variables locales son aquellas creadas en secciones var en los cuerpos de las funciones, incluyendo a los parámetros. Las variables locales se superponen a las globales. A diferencia de C y Java, JavaScript no tiene diferente alcance (scope) en cada bloque de código. Mejor dicho, los bloques de JavaScript lo definen las mismas funciones y no las parejas { }, así todas las variables de una función comparten el mismo alcance, indiferentemente del nivel de anidamiento de llaves en que se declaren las variables. En el siguiente ejemplo; las variables o, i, j, k comparten el mismo alcance.

    function test(obj)
    {
       var i = 0;                        // i is defined throughout function
       if (typeof obj == "object")
       {
          var j = 0;                     // j is defined everywhere, not just block
          for(var k=0; k < 10; ++k)   // k is defined everywhere, not just loop
          {
             document.write(k);
          }
          document.write(k);             // k is still defined: prints 10
       }
       document.write(j);                // j is defined, but may not be initialized
    }
    

    Las variables locales ocultan las globales, y las locales tienen alcance (scope) de toda la función. Esto puede generar resultados confusos o sorprendentes. Por ejemplo:

    var str = "global";
    function f( )
    {
       alert(str);         // Displays "undefined", not "global"
       var str = "local";  // Variable initialized here, but defined everywhere
       alert(str);         // Displays "local"
    }
    f( );
    

    En la función anterior no se imprime "global" en el primer alert de la línea 4, porque se está usando la variable str local de la línea 5, la cual no ha sido inicializada aún. Este comportamiento equivale al siguiente código:

    var str = "global";
    function f( )
    {
       var str;
       alert(str);         // Displays "undefined", not "global"
       str = "local";      // Variable initialized here, but defined everywhere
       alert(str);         // Displays "local"
    }
    f( );
    

    El consejo de mantener las variables tan locales como sea posible, no aplica para JavaScript. Algunos programadores prefieren incluso declarar sus variables juntas al inicio de cada función JavaScript. Es importante que el programador tenga claro que en JavaScript una variable es global si no aparece en el cuerpo de una función, y que una variable es local si aparece en algún lugar del cuerpo de una función, no importa el nivel de anidamiento de llaves en que se encuentre.

    En JavaScript una variable puede estar declarada o no declarada. Una variable es declarada si el programador lo hace formalmente en una sección var, incluso aunque no sea inicializada (en cuyo caso JavaScript le asigna el valor undefined). Naturalmente una variable no declarada es la que no aparece en una sección var. Intentar acceder a una variable no declarada es siempre un error y el intérprete podría detener la ejecución del script. Sin embargo, el comportamiento de asignar una variable no declarada depende del modo de ejecución del script. En modo estricto se considerará un error, mientras que en modo no estricto (el por defecto), JavaScript creará una nueva variable global, le asignará el valor, y a partir de ese momento se comportará como una variable declarada.

    var x;     // Una variable declarada no inicializada. Su valor es undefined
    alert(u);  // Tratar de leer una variable no declarada es un error
    c = 2413;  // Asignar un valor a una variable no declarada crea una variable global
    

    Cuando se inicia un intérprete de JavaScript, lo primero que hace antes de ejecutar el código fuente, es crear el objeto global (global object), que es un objeto como cualquier otro con la misión de almacenar todas las variables globales que se declaren en el código. Cada vez que se declara una variable global, realmente está declarando una propiedad del global object.

    El intérprete de JavaScript también define convenientemente otras propiedades (variables y métodos) en el global object que son de uso común, como NaN, Infinity, parseInt(), y Math. La palabra reservada this usada en un contexto global (fuera del cuerpo de una función), referencia específicamente al global object. En el caso del navegador, el global object es además la ventana del navegador (window), y se cumple:

    this == this.window == window
    

    Si las variables globales son propiedades de un global object ¿qué son las variables locales? También son propiedades, pero de un objeto temporal llamado call object, el cual se construye en cada invocación de una función, y mantiene juntos los parámetros, variables declaradas en el cuerpo de la función y el código de la misma. Este objeto es el que impide que las variables locales sobrescriban las globales cuando tienen el mismo nombre. Dentro de una función, this hace referencia al call object en lugar del global object.

    5 pts.

    Cree un script en el encabezado head de un documento HTML5. En el primer script cree una variable con su número de la suerte y una función que lo imprime en un párrafo. Cree un segundo script como el último elemento del cuerpo del documento (body).

    Expresiones

    Una expresión es un trozo de código que al evaluarse genera un valor de cualquiera de los 7 tipos de datos de JavaScript. Las expresiones pueden ser tan sencillas como valores literales, hasta expresiones complejas compuestas de varios operadores y operandos.

    Operadores

    Los operadores aritméticos de resta (-), multiplicación (*), división (/) y módulo (%) sólo trabajan con números. Si se usan con operandos de otro tipo de datos, JavaScript intentará convertirlos a números invocándoles el método valueOf(). Ya que todo número en JavaScript es un flotante, la división siempre es real, es decir, no hay división entera como ocurre en otros lenguajes. El operador + actúa como operador de suma si sus operandos son numéricos, y como el operador de concatenación si al menos uno de ellos es string.

    JavaScript tiene dos operadores para determinar semejanza. El operador de igualdad (==) indica si dos valores son el mismo incluso tras hacer conversiones de tipos. El operador de identidad (===) es más estricto y dice que dos valores son idénticos si son iguales y tienen el mismo tipo de datos. Sus opuestos respectivos son el operador de desigualdad (!=) y de no identidad (!==). Por ejemplo:

    "0" ==  false        // produce true  (hace conversiones)
    "0" === false        // produce false (no hace conversiones)
    
    "037.50" ==  37.5    // produce true
    "037.50" === 037.50  // produce false
    

    Los operadores de comparación <, <=, >, >= actúan como de costumbre si sus operandos son del mismo tipo, sino se tratan de convertir a números, y si esta conversión falla se generará NaN, que siempre se evalúa como false en un contexto booleano.

    1 + 2        // Addition. Result is 3.
    "1" + "2"    // Concatenation. Result is "12".
    "1" + 2      // Concatenation; 2 is converted to "2". Result is "12".
    11 < 3       // Numeric comparison. Result is false.
    "11" < "3"   // String comparison. Result is true.
    "11" < 3     // Numeric comparison; "11" converted to 11. Result is false.
    "one" < 3    // Numeric comparison; "one" converted to NaN. Result is false.
    

    Los operadores lógicos son los mismos de C/C++/Java para conjunción (&&), disyunción (||) y negación (!). Pero guardan una diferencia. Los operandos se convierten a Boolean temporalmente para hacer la evaluación del operador, pero el resultado final del operador no es booleano, sino del tipo de datos del último operando evaluado. A esto se le puede sacar ventaja. En el siguiente ejemplo, la variable max adquirirá el primer valor numérico que no sea null en lugar de un valor booleano.

    // If max_width is defined, use that. Otherwise look for a value in
    // the preferences object. If that is not defined use a hard-coded constant.
    var max = max_width || preferences.max_width || 480;
    

    El operador ternario ?: funciona igual que en C. Los operadores de bits (bitwise operators): and (&), or (|), xor (^), not (~), shift left (<<), signed shift right (>>) y shift right with zero fill (>>>), sólo trabajan con enteros de 32 bits y su uso es inusual en JavaScript.

    El operador in recibe al lado izquierdo un string y al derecho un objeto o arreglo. Se evalúa como true si el string es el nombre de una propiedad del objeto al lado derecho, incluso si es heredada. Ejemplos:

    var point = { x:1, y:1 };        // Define an object
    var has_x_coord = "x" in point;  // Evaluates to true
    var has_y_coord = "y" in point;  // Evaluates to true
    var has_z_coord = "z" in point;  // Evaluates to false; not a property of point
    var tstr = "toString" in point;  // Inherited property from Object; evaluates to true
    

    El operador instanceof espera al lado izquierdo un objeto y al lado derecho el nombre de una clase (realmente una función constructora). Evalúa a true si el lado izquierdo (el objeto) es una instancia de la clase o un objeto descendiente de dicha clase.

    var d = new Date();  // Create a new object with the Date() constructor
    d instanceof Date;   // Evaluates to true; d was created with Date()
    d instanceof Object; // Evaluates to true; all objects are instances of Object
    d instanceof Number; // Evaluates to false; d is not a Number object
    
    var a = [1, 2, 3];   // Create an array with array literal syntax
    a instanceof Array;  // Evaluates to true; a is an array
    a instanceof Object; // Evaluates to true; all arrays are objects
    a instanceof RegExp; // Evaluates to false; arrays are not regular expressions
    

    El operador unario typeof genera un string con el nombre del tipo de datos de su argumento a la derecha, que puede o no estar entre paréntesis. Típicos resultados son "number", "string", "boolean", "object" (incluye los arreglos y null), "function" y "undefined" cuando el parámetro no ha sido declarado. Ejemplo:

    typeof undefined      // retorna "undefined"
    typeof false          // retorna "boolean"
    typeof (3 / 1.1)      // retorna "number"
    typeof '0'            // retorna "string"
    typeof {}             // retorna "object"
    typeof []             // retorna "object"
    typeof null           // retorna "object"
    typeof function() {}  // retorna "function"
    
    return typeof value == "string" ? "'" + value + "'" : value;
    

    Ya que typeof retorna "object" para objetos de diversa naturaleza, su utilidad se reduce a saber si su operando es o no de un tipo de datos primitivo. Para saber la clase de un objeto hay que recurrir a otras técnicas como el operador instanceof, o la propiedad Object.constructor, como se verá luego.

    Igual que en C++ y Java, el operador new crea un objeto vacío, invoca un constructor para que lo inicialice, y retorna la dirección de memoria del objeto. Si el constructor no recibe parámetros, puede omitirse sus paréntesis.

    o = new Object;    // Optional parentheses omitted here
    d = new Date();    // Returns a Date object representing the current time
    
    // Una función constructora
    function Point(x, y)
    {
       this.x = x;
       this.y = y;
    }
    
    p = new Point(3.0, 4.0);   // Crea un objeto vacío y lo inicializa con la función constructora
    

    El operador delete intenta eliminar una propiedad de un objeto, un elemento de un arreglo o la variable especificada como operando. Retorna true si la eliminación fue exitosa. Algunas variables o propiedades no pueden ser eliminadas, como las declaradas en secciones var. Si el único argumento de delete no existe, se retorna true. Ejemplos:

    var o = {x:1, y:2};  // Define a variable; initialize it to an object
    delete o.x;          // Delete one of the object properties; returns true
    typeof o.x;          // Property does not exist; returns "undefined"
    delete o.x;          // Delete a nonexistent property; returns true
    delete o;            // Can't delete a declared variable; returns false
    delete 1;            // Can't delete an integer; returns true
    x = 1;               // Implicitly declare a variable without var keyword
    delete x;            // Can delete this kind of variable; returns true
    x;                   // Runtime error: x is not defined
    

    Nótese que el operador delete no trata de eliminar memoria dinámica como ocurre en C++, lo cual se hace en JavaScript con el recolector de basura (garbage collector), no hay otra forma. El delete de JavaScript elimina una propiedad, es decir, deja de existir; no es que se le asigne undefined:

    var my = new Object( );   // Create an object named "my"
    my.hire = new Date( );    // my.hire refers to a Date object
    my.fire = my.hire;        // my.fire refers to the same object
    delete my.hire;           // hire property is deleted; returns true
    document.write(my.fire);  // But my.fire still refers to the Date object
    

    Sentencias

    Una sentencia en un lenguaje de programación es un comando que al ser ejecutado produce un efecto. Un programa es una secuencia de sentencias que se ejecutan una tras otra, sin embargo, el lenguaje de programación provee estructuras de control que permiten ejecutar sentecias condicional o repetitivamente. Las sentencias en JavaScript deben terminar en punto y coma, aunque no es obligatorio. Las estructuras de control condicional son las mismas de C: if, else y switch, como muestra el siguiente ejemplo.

    if (username != null)
       alert("Hello " + username + "\nWelcome to my blog.");
    else if ( askname )
    {
       username = prompt("Welcome!\n What is your name?");
       alert("Hello " + username);
    }
    else
       alert("Hello there");
    

    La estructura de control switch es más flexible que en C. Al ser un lenguaje interpretado, los casos del switch son expresiones, no sólo constantes. El parámetro del switch se compara contra cada case utilizando el operador de identidad ===, hasta encontrar una coincidencia o el default si fue provisto por el programador.

    Una estructura switch en JavaScript

    En muchas situaciones una estructura switch puede ser reemplazada por un arreglo común o un arreglo asociativo (también conocido como tabla de dispersión (hash), o mapa (map)) si trabaja con valores primitivos; o por polimorfismo si el switch trabaja con objetos; lo cual tiene algunas ventajas como mejor mantenimiento del código. Por ejemplo, el mismo efecto del js_switch_normal puede conseguirse con un arreglo asociativo como se muestra en el js_switch_hash.

    Utilizar una tabla de dispersión para reemplazar un switch

    El js_switch_hash construye un objeto normal, llamado methods, cuyas propiedades son referencias a funciones, y que se pueden acceder con el operador punto, por ejemplo methods.ftp(), o con el operador corchetes methods['ftp'](). Ya que la función transfer() (línea 14) recibe el método de transferencia como un string, resulta conveniente utilizar el operador corchetes. La línea 16 pregunta si el objeto methods tiene una propiedad con el nombre del método enviado por parámetro. Si es así, accede a la propiedad con el operador corchetes [], la cual se evalúa como una función, y finalmente con el operador paréntesis () la invoca pasándole el archivo (file) por parámetro.

    Dado a que las funciones transfer_method() son globales, JavaScript las declara ya como propiedades de un objeto global (window en el caso del navegador). De esta forma, el programador podría acceder a ellas directamente como se ve en el js_switch_global_object, en lugar de crear el objeto methods.

    Utilizar el objeto global window como tabla de dispersión

    Como es de esperar, las estructuras de control para reiteración de sentencias de JavaScript son las mismas de C: for, while y do/while. Por ejemplo:

    ');
    ]]>
    Ejemplo del ciclo for en JavaScript. Correr este ejemplo.

    5 pts. Cree una función JavaScript que reciba un número n y retorne un arreglo con los primeros n números de Fibonacci. Utilice su función para crear un arreglo con los primeros 40 números de Fibonacci e imprímalos en una lista ordenada.

    JavaScript provee un tipo de ciclo más, el ciclo for/in, cuya sintaxis es:

    for (property_name in object)
       /* do something with */ object[property_name];
    

    donde property_name se refiere al nombre de una variable, una declaración var, un elemento de un arreglo, la propiedad de un objeto, o incluso una expresión. object se refiere a un objeto o una expresión que evalúa en un objeto. El cuerpo del for/in, se ejecuta una vez por cada propiedad del objeto y en cada iteración, property_name adquiere el nombre de la propiedad y no su valor, es decir, property_name es siempre un string. Para obtener el valor de la propiedad debe entonces utilizarse el operador corchetes [] de la forma object[property_name]. Por ejemplo:

    Recorrer las propiedades de un objeto con el ciclo for/in. Correr ejemplo completo.
    5 pts.

    Utilice un ciclo for/in para imprimir recursivamente los activos del inventario que hizo en el inventory_json.

    Ya que property_name puede ser una expresión arbitraria, cada vez que se itera se podría evaluar de forma diferente. Por ejemplo, este código copia los nombres de un objeto en un arreglo:

    var obj = {x:1, y:2, z:3};
    var arr = new Array( );
    var i = 0;
    for( arr[i++] in obj )
       ;
    
    // Imprime cada nombre en el arreglo
    for(i in arr) alert(i);
    

    No hay forma de especificar en un for/in el orden de recorrido, es dependiente de la implementación de JavaScript; lo único que asegura el estándar es que se recorrerán todas las propiedades enumerables del objeto. Una propiedad es enumerable si es definida por el objeto mismo, es decir, no es heredada de otro objeto.

    100 pts.

    Cree un juego de funciones que cada vez que son invocadas generan un valor aleatorio de uno de los siete tipos de datos de JavaScript: valor especial, booleano, número, string, función, objeto, y arreglo. Por ejemplo:

    function genSpecialValue() { return Math.random() < 0.5 ? null : undefined; }
    function genBoolean() { ... }
    function genNumber() { ... }
    function genString() { ... }
    function genFunction() { ... }
    
    function genObject() { ... }
    function genArray() { ... }
    
    function genRandomValue() { ... }
    

    Cree una función genRandomValue(), que invoca aleatoriamente a cualquiera de las funciones anteriores para retornar un valor aleatorio de un tipo de datos aleatorio. Utilice un arreglo para invocar las funciones y no una estructura de control switch.

    La función genFunction() debe crear una función con un cuerpo diferente cada vez que se invoque. Estudie la función constructora new Function(params, body).

    Modifique genArray() para que genere un arreglo con n elementos aleatorios, cada uno resultado de invocar a genRandomValue().

    Modifique genObject() para que genere un objeto con n propiedades aleatorias. Haga que el nombre de la propiedad sea un string aleatorio generado por genString() (considere si se debe mejor implementar una función genIdentifier()). Haga que el valor de la propiedad sea generado por genRandomValue(). Utilice la notación de corchetes para crear las propiedades del objeto.

    75 pts.

    Provea un juego de funciones de impresión printXXX(), cada una especializada en escribir en el documento un tipo de datos utilizando la notación de objetos de JavaScript (JSON). Por ejemplo printArray(array) y printObject(object). Pueda que una función le puede servir para varios tipos de datos que se comportan de forma similar.

    Debe además proveer una función printValue(value) que recibe por parámetro un valor de cualquier tipo de datos, e imprimirlo en el documento invocando a la función printXXX() correspondiente. Estudie los operadores typeof e instanceof. Note que las invocaciones entre printValue() y las funciones printXXX() deberán generar una recursión en caso de tener objetos o arreglos involucrados.

    Idee algún mecanismo para mantener una indentación impecable en el resultado. Por ejemplo, al imprimir un arreglo con elementos aleatorios, podría verse:

    [
    	8.1,
    	false,
    	{
    		dicob: null,
    		pavuwe:
    			[
    			]
    		gofirute: "hekava",
    	},
    	function anonymous( /**/) { return "gisite"; },
    ],
    

    Note que los identificadores de las propiedades en el ejemplo anterior son textos aleatorios. Sugerencia. Para imprimir una función, conviértala a un string y reemplace los cambios de línea por espacios en blanco utilizando expresiones regulares. Por ejemplo: function.toString().replace(/\s+/g, ' ').

    10 pts. Invoque a su función genArray() o genObject() para generar un arreglo de elementos aleatorios o un objeto con propiedades aleatorias. Imprima el objeto o arreglo como contenido de un elemento pre en el documento. Debe ocurrir que cada vez que se refresque el documento, el navegador genere una estructura de datos distinta, con niveles de composición aleatorios e indentación impecables.

    [Contenido pendiente: Manejo de excepciones (try/catch/finally)].

    Arreglos y funciones como objetos

    En JavaScript los arreglos y las funciones son objetos, y por ende tienen propiedades heredadas de las pseudoclases Array y Function respectivamente. A su vez, estas pseudoclases heredan de Object. Esta sección da una introducción de algunas propiedades que estas clases proveen.

    En JavaScript los arreglos y las funciones son objetos, con propiedades y métodos.

    Propiedades de Object

    Todos los objetos de JavaScript heredan de la pseudoclase Object, y por ende, todos los objetos de JavaScript tienen las propiedades que esta pseudoclase defina. En lo siguiente se presentarán algunas de ellas. Cuando se crea un objeto siempre se guarda una referencia a la función que se utilizó para inicializar el objeto, en la propiedad constructor. Por ejemplo:

    var d1 = new Date();
    d1.constructor == Date    // Se evalúa como true
    

    La propiedad constructor sirve para saber si un objeto es instancia directa de una psudoclase particular, mientras que el operador instanceof sirve para determinar si el objeto es descendiente de una pseudoclase dada. Por ejemplo:

    var d1 = new Date();
    d1.constructor == Date    // true
    d1.constructor == Object  // false
    d1 instanceof Date        // true
    d1 instanceof Object      // true, todo objeto es descendiente de Object
    d1 instanceof Array       // false
    
    var o1 = new Object();
    var o2 = {};
    o1.constructor == Object  // true
    o2.constructor == Object  // true
    o1 instanceof Object      // true
    o2 instanceof Object      // true
    
    var a1 = new Array();
    var a2 = [];
    a1.constructor == Array   // true
    a2.constructor == Array   // true
    a1.constructor == Object  // false
    a2.constructor == Object  // false
    a1 instanceof Array       // true
    a2 instanceof Array       // true
    a1 instanceof Object      // true
    a2 instanceof Object      // true
    
    function f1() {};
    var f2 = function() {};
    var f3 = new Function('', ';');
    f*.constructor == Function // true
    f*.constructor == Object   // false
    f* instanceof Function     // true
    f* instanceof Object       // true
    

    El método toString() heredado de la pseudoclase Object es invocado automáticamente por JavaScript cuando necesita convertir el objeto en un string. La implementación por defecto retorna la cadena "[object Object]" que es poco significativa. Por ende, este método debería ser sobrescrito por clases descendientes.

    El método valueOf() es invocado automáticamente por JavaScript cuando el objeto se utiliza en un contexto numérico. El programador también debería sobrescribirlo para clases propias, si su objeto puede convertirse automáticamente a un número.

    Algunas propiedades y métodos que JavaScript provee por defecto en los objetos, arreglos y funciones.

    Propiedades de Array

    La propiedad más natural de un arreglo es length que indica la cantidad de elementos que hay almacenados en él. De hecho es la principal diferencia entre un arreglo y un objeto tradicional. La tabla js_array_methods muestra una lista de métodos que cada arreglo hereda de la pseudoclase Array. Es importante resaltar que algunos de estos métodos modifican el arreglo directamente.

    Método Detalles
    push(elems) Inserta los elementos enviados por parámetro al final del arreglo. Retorna la cantidad de elementos que quedan en el arreglo.
    [1,2,3].push(9,null) // retorna 5, deja [1,2,3,9,null]
    pop() Elimina y retorna el último elemento del arreglo. Si está vacío, retorna undefined.
    [1,2,3].pop() // deja [1,2]
    unshift(elems) Inserta los elementos enviados por parámetro al inicio del arreglo. Retorna la cantidad de elementos que quedan en el arreglo.
    [1,2,3].unshift(9,null) // retorna 5, deja [9,null,1,2,3]
    shift() Elimina y retorna el primer elemento del arreglo
    [1,2,3].shift() // deja [2,3]
    join(sep) Retorna un string resultado de concatenar todos los elementos en el arreglo, separados por la cadena sep, o comas si se omite.
    [1,2,3].join() // genera "1,2,3"
    [1,2,3].join('; ') // genera "1; 2; 3"
    toString() Está sobrescrito para hacer lo mismo que join().
    [1,[8,9],2].toString() // retorna "1,8,9,2"
    reverse() Invierte el orden de los elementos en el arreglo.
    [1,2,3].reverse() // genera [3,2,1]
    sort(comp) Ordena los elementos en el arreglo de acuerdo a la función comparadora comp(), sino los ordena alfabéticamente invocando toString() a cada uno de sus elementos.
    [7, 200, 81].sort() // genera [200,7,81]
    [7, 200, 81].sort(function(a,b){return a-b;}) // genera [7,81,200]
    slice(i,j) Retorna un nuevo subarreglo con los elementos encontrados desde la posición i hasta j - 1, es decir, el que está en j no se incluye.
    [1,2,3,4,5].slice(1,3) // genera [2,3]
    Métodos de la pseudoclase Array

    El operador corchetes [] se utiliza en JavaScript tanto para acceder a los elementos de un arreglo, como a las propiedades de un objeto. De esta forma, un objeto podría utilizarse para simular un arreglo, almacenando los elementos en propiedades con nombres numéricos ('1', '2', '3', ...), más una propiedad length asignada de manera acorde a la cantidad de elementos alojados en el objeto. Por ejemplo:

    Objetos tipo arreglo. Correr este ejemplo.

    Cada vez que la función generarNombres() del js_array_like_objects_example es invocada, creará un objeto vacío resultado (línea 5), el cual llenará con número aleatorio de propiedades cuyo nombre son precísamente números secuenciales. En la línea 11 le crea una propiedad length y retorna una referencia al objeto. Las líneas 16 a 20 crean uno de estos objetos y lo utilizan como si fuese un arreglo normal. Sin embargo no lo es, hay algunas diferencias importantes, en especial que carece de los métodos heredados de la pseudoclase Array, y por ende el objeto tipo arreglo (array-like object) tiene poca funcionalidad para ser modificado. Es decir, se ha creado un arreglo de "sólo lectura", lo cual es una cualidad importante en JavaScript y ampliamente explotada por los navegadores para acceder al modelo de objetos del documento (DOM, Document Object Model) como se verá adelante.

    Propiedades de Function

    En JavaScript las funciones son objetos y por ende también tienen propiedades. Una función puede ser invocada con igual, menos o más argumentos de los esperados por la función; lo cual no genera un error de sintaxis. Por el contrario, JavaScript permite al programador determinar la cantidad real de parámetros enviados, y por tanto, es fácil implementar funciones que trabajan adecuadamente con cantidades arbitrarias de argumentos.

    Cada vez que se invoca una función, JavaScript llena un objeto tipo arreglo (array-like object) llamado arguments que es accesible desde la función. Como es de esperar arguments.length indica la cantidad de argumentos con que se invocó la función y la expresión arguments[i] accede al argumento i + 1 en la lista. El siguiente es un ejemplo de una función tradicional:

    Cómo determinar la cantidad de parámetros con que se invoca una función

    Como se ve en el ejemplo anterior, la invocación de min() con tres parámetros falla el resultado esperado por el llamador. La función min podría detectar esto con la propiedad length, heredada de Function, la cual indica la cantidad de parámetros especificados en la declaración de la función, tal como lo hizo el programador. Mientras que la propiedad arguments.length indica la cantidad de argumentos con que se hizo una invocación de la función. De acuerdo al js_function_arguments1, min.length siempre es dos, incluso aunque no se invoque la función nunca; mientras que el valor de arguments.length puede variar en cada invocación de la función. Cuando estas dos cantidades difieren, el programador puede saber que se ha invocado la función con menos o más argumentos de los esperados y reaccionar de alguna forma, por ejemplo:

    Una función que exige un número estricto de argumentos. Correr este ejemplo.

    Sin embargo, en JavaScript es fácil escribir una función que trabaje naturalmente con un número arbitrario de parámetros, como se ve a continuación.

    Una función que recibe un número arbitrario de argumentos. Correr este ejemplo.
    10 pts.

    Escriba una función que puede recibir una cantidad arbitraria de parámetros y calcula su promedio. Si es invocada sin parámetros retorna undefined. Llame su función con cero, uno, dos y más parámetros e imprima el resultado en el documento. Compruebe que los resultados sean correctos.

    Ya que una función es un objeto, puede tener propiedades, como la propiedad length que es creada por la pseudoclase Function, y el arreglo arguments visto anteriormente. El programador también puede crear sus propias propiedades, de la misma forma que se hace con cualquier otro objeto. Las propiedades que el programador defina tendrán el efecto de ser "variables estáticas" de la función. El siguiente ejemplo muestra una función que reporta el número de veces que ha sido invocada.

    countCalls() ha sido llamada ' + ++countCalls.count + ' veces

    '); } countCalls(); // Imprime "countCalls() ha sido llamada 1 veces" countCalls(); // Imprime "countCalls() ha sido llamada 2 veces" countCalls(); // Imprime "countCalls() ha sido llamada 3 veces" ]]>
    Simular una variable estática dentro de una función. Correr este ejemplo

    Pseudoclases

    JavaScript no tiene el concepto de clase como sí C++ o Java, sino que las clases se simulan con funciones inicializadoras, objetos y prototipos de objetos. Una función constructora o función inicializadora es una función cualquiera que recibe un objeto por parámetro this y le crea propiedades y métodos. Por ejemplo:

    Una función constructora para simular una clase Rectángulo

    La función Rectangle() del js_constructor_function es una función global como cualquier otra, y por ende, es una propiedad del objeto global (window, en caso de los navegadores web). Si se invoca directamente, el valor de this será el objeto global (window) y las líneas 5 y 6 crearán en él dos nuevas propiedades width y height (si no existen ya), y la línea 9 crea otra propiedad más que almacena una función. Como se puede deducir, invocar una función constructora directamente carece de sentido.

    Las funciones constructoras deben ser invocadas con el operador new. Este operador hace lo siguiente. Crea un objeto vacío, sin propiedades. Invoca a la función constructora pasándole el objeto vacío por parámetro para que lo incialice. La función constructora recibe entonces el nuevo objeto en su parámetro this y crea en él las propiedades esperadas. Una vez que la función constructora haya terminado su ejecución, el operador new retorna la dirección de memoria del nuevo objeto inicializado.

    var rect1 = new Rectangle(2, 4);    // rect1 = { width:2, height:4 };
    var rect2 = new Rectangle(8.5, 11); // rect2 = { width:8.5, height:11 };
    document.write("Area of rect2: " + rect2.area() ); // Imprime "Area of rect2: 93.5"
    

    Todos los objetos que sean inicializados con la función Rectangle() tendrán un width y un heigth, el desarrollador puede utilizar con confianza estas propiedades. La función constructora no debe retornar un valor, ya que reemplazará el resultado de la expresión new. Nótese que nunca hubo una clase, sino una función constructora y objetos tradicionales que son inicializados con ella. Por esto se les llama pseudoclases y la función constructora es quien da nombre a la pseudoclase.

    Como es de esperarse, todos los objetos inicializados con Rectangle() tienen su propia copia independiente de width y height. Esto implica que en el ejemplo anterior, si a rect2.width se le asigna otro valor, no afectará al valor de rect1.width; como podría esperarse. Sin embargo, esto mismo ocurre con los métodos: cada objeto inicializado con la función constructura Rectangle() tendrá su propia copia independiente del método area(), lo cual es ineficiente. Esta redundancia se elimina con los objetos prototipo.

    Todas las funciones que el programador defina, tendrán automáticamente otra propiedad llamada prototype creada por Function, de la misma forma que length y arguments. La propiedad prototype es un objeto, definido como una "variable estática" y por tanto, todas las invocaciones a la función tendrán acceso al mismo prototype.

    La especificación ECMAScript exige a cada intérprete de JavaScript implementar el siguiente comportamiento. Cuando se intenta acceder a una propiedad prop en el objeto obj, de la forma obj.prop u obj['prop'], el intérprete buscará si obj tiene una propiedad prop declarada en él, y en tal caso la usará. De lo contrario, el intérprete buscará la propiedad prop en el objeto prototipo de obj, es decir, en obj.prototype.prop; si está declarada la usará, de lo contrario buscará la propiedad en el prototipo del prototipo (obj.prototype.prototype.prop), y así recursivamente hasta encontrar la propiedad o hasta que un prototipo sea null.

    En otras palabras. Si el intento de acceder a la propiedad obj.prop es exitoso, es porque obj tiene una propiedad prop, o alguno de los prototipos de obj define la propiedad prop. Este comportamiento es aproximadamente el mismo que el logrado por la herencia de clases, y es la cadena de prototipos es el mecanismo que JavaScript utiliza para simular herencia.

    Cada vez que se invoca el operador new con una función constructora, éste implícitamente crea una propiedad prototype al nuevo objeto y hace que apunte al prototype de la función constructora. De esta forma, cualquier propiedad que sea definida en el objeto prototipo de la función constructora (o pseudoclase), será compartida por todos los objetos inicializados con dicha pseudoclase. El siguiente pseudocódigo explica lo que hace el operador new internamente:

    // El programador define una función constructora
    function Constructor(a,b) { this.prop1 = a; this.prop2 = b; }
    
       // La pseudoclase Function inserta una propiedad length en la función implícitamente
       Constructor.length = 2;
    
       // La pseudoclase Function inserta una propiedad prototype en la función implícitamente
       Constructor.prototype = {};
    
    
    // El programador crea un nuevo objeto con su función constructora
    var obj1 = new Constructor(a,b);
    
       // El operador new hace esto internamente:
    
       // 1. Crea un objeto vacío
       var newObject = {};
    
       // 2. Crea una propiedad prototype en el objeto que apunta al prototipo de la función
       newObject.prototype = Constructor.prototype;
    
       // 3. Invoca a la función constructora para que inicialice el nuevo objeto, la cual
       // creará las propiedades prop1 y prop2
       Constructor.call(newObject, a, b);
    
       // 4. Retorna el objeto recién inicializado
       return newObject;
    
    // obj1 tendrá al final las siguientes propiedades:
    obj1.prop1
    obj1.prop2
    obj1.prototype
    

    En resumen, cualquier propiedad asignada al objeto Constructor.prototype, será automáticamente una propiedad compartida por todos los objetos construidos con la función Constructor, incluso, aunque dicha propiedad sea asignada después de que los objetos fueron creados. De esta forma, las propiedades creadas por la función constructora directamente en el objeto serán copias independientes en cada objeto, y las propiedades creadas en el prototipo de la misma, serán propiedades compartidas por todos los objetos (lo que en C++ y Java se conoce como miembros estáticos). Por ejemplo:

    // Construye un rectángulo. Recibe un objeto en el parámetro oculto this
    function Rectangle(w, h)
    {
       // Cada objeto tendrá una copia independiente de estas propiedades
       this.width = w;
       this.height = h;
    }
    
    // Este método será compartido por todos los objetos creados con new Rectangle()
    Rectangle.prototype.area = function() { return this.width * this.height; }
    

    Nótese que el método area() no se definió dentro de la función constructora. Si eso se hubiera hecho, se haría la asignación por cada rectángulo creado durante la ejecución del programa, lo cual es ineficiente.

    El prototype es el lugar ideal para definir métodos, constantes y variables compartidas por todos los objetos de una misma pseudoclase. El programador querrá la mayor parte del tiempo tratar estas propiedades como de sólo lectura. Si modifica una propiedad en el prototipo directamente, el cambio afectará a todos los demás objetos inmediatamente; pero si intenta modificar la propiedad a través de un objeto cualquiera, creará una nueva propiedad en ese objeto que oculta la del prototipo. Por ejemplo:

    Conflicto de propiedades en el objeto y el prototipo. Correr este ejemplo.

    Idealmente cuando un programador define una pseudoclase, debe redefinir algunos métodos heredados de la pseudoclase Object. El método toString() debe regresar una representación "string" del objeto. Opcionalmente puede redefinir el método parse(). Si su pseudoclase se puede convertir en un valor primitivo, implemente también valueOf().

    80 pts.

    Escriba una jerarquía de pseudoclases (funciones constructoras) que generen un documento HTML válido de longitud y estructura aleatoria. Por ejemplo, comience con la pseudoclase Document. Su función constructora crea las propiedades de un documento típico: el encabezado y el cuerpo, los cuales son a su vez otros objetos construidos con pseudoclases.

    La función constructora de Body crea un arreglo de una cantidad aleatoria de elementos hijos escogidos también aleatoriamente. Para efectos de este ejercicio, basta con la siguiente jerarquía.

    Document: Header, Body
    Header: Style
    Body: (Paragraph|Heading|List)+
    List: ListItem+
    ListItem: Text|Paragraph+|List
    

    De esta forma cada elemento de su jerarquía tendrá al menos: un arreglo de elementos hijos (llámese children); y un método print() que imprime el elemento usando document.write(), y propaga el llamado a todos sus hijos. El método print() debe declararse en el prototipo de la función constructora para que sea compartido por todos los objetos de la misma pseudoclase.

    Escriba un documento HTML que cree un objeto Document y llame su método print(), el cual debe imprimir el cuerpo del documento. Sugerencia, puede utilizar el siguiente ejemplo:

    
    
    
       Random document
       
       
    
    
    
       
    
    
    ]]>
    40 pts.

    Implemente en su jerarquía de elementos una pseudoclase Style que es hijo de Header y que se encarga de generar estilos CSS aleatorios en el documento, de tal forma que al refrescar, la apariencia del mismo sea compleamente aleatoria. Para esto, agregue un método printHeader() a su pseudoclase Document, el cual invoca al print() de Header y éste al de Style.

    Programación del navegador (el objeto window)

    JavaScript es un lenguaje genérico que cualquier software puede implementar para permitir al usuario un medio de automatizar tareas del software mismo. Aunque el número de sistemas que implementan JavaScript crece en el tiempo, han sido los navegadores web el ejemplo clásico. El navegador web utiliza el lenguaje de programación JavaScript para exponer cierta funcionalidad al autor de un sitio web, quien debe aprovecharla sólo para mejorar la experiencia del usuario con su obra, haciéndole más fácil obtener o transmitir información. Por ejemplo, creando efectos visuales que guíen al usuario hacia lo busca; ordenando los valores de una tabla para ayudarle a encontrar lo que necesita, ocultado o mostrando información que le es o no de interés, o intercambiando información con el servidor web de tal forma que el usuario no se confunda con una recarga completa de la página.

    Ha ido ganando popularidad una práctica de programación llamada Unobtrusive JavaScript (JavaScript no impertinente). Este paradigma sustenta que JavaScript no debería tratar de llamar la atención a sí mismo. No consiste en una variación del lenguaje, sino en un conjunto de prácticas recomendadas, por ejemplo:

    El ambiente de desarrollo del navegador

    Utilizando JavaScript, el navegador pone a disposición del autor un conjunto de objetos que permiten manipular ventanas, cambiar el URL, modificar el contenido o estilo del documento, comunicarse con el servidor web, y otras operaciones útiles.

    El objetivo primordial de un navegador web es desplegar al usuario documentos (X)HTML en una ventana. De ahí nacen los dos objetos más importantes para el programador: la ventana del navegador (window), la cual contiene al documento web (document) del autor.

    El objeto window es quizá el más importante, porque no sólo es un simple objeto, es el objeto global. Todas las propiedades del objeto global son variables globales, y viceversa: las variables globales que el programador defina se crearán como propiedades del objeto global window. Así, las dos siguientes declaraciones fuera del cuerpo de una función hacen esencialmente lo mismo:

    var answer = 722;
    window.answer = 722;
    

    De lo anterior se obtiene que da lo mismo escribir window.document que simplemente document. Dicho de otra forma, el objeto window es el más importante porque todos los demás objetos que existen se acceden a através de él, como se ve en la siguiente jerarquía parcial:

    window
       parent
       top
       navigator
       location
       history
       screen
       document
          anchors[]
          links[]
          images[]
          applets[]
          forms[]
             elements[]
    

    La jerarquía que nace del objeto document ha sido estandarizada por el Consorcio Web (W3C) en lo que se llama Document Object Model (DOM), de tal forma que permite a los programadores JavaScript escribir código portable entre navegadores para manipular el documento y sus estilos.

    Como se indicó al iniciar este capítulo, el código JavaScript puede correr en cuatro lugares: en un elemento script, en un archivo .js externo, en atributos manejadores de eventos y en el pseudoprotocolo javascript:. Todos comparten el mismo objeto global window y su descendencia; por ende, cualquier propiedad definida en uno de estos lugares, es accesible en todos los demás. En el siguiente ejemplo, la función global genRandom() es definida en el primer script y usada en el segundo script.

    
    
       Global properties
       
    
    
    
       

    Su número de la suerte es: .

    ]]>
    Variables globales son propiedades del objeto window. Correr este ejemplo.

    El modelo de ejecución de JavaScript

    El código almacenado en los elementos script se ejecuta sólo una única vez: cuando el documento (X)HTML es cargado en el navegador. Una vez que se ha completado la carga, la interacción se realiza mediante eventos. Cuando ocurre un evento, normalmente generado por el usuario, el navegador puede invocar código JavaScript del autor que reaccione al evento, el cual se asocia al elemento que genere eventos a través de atributos intrínsecos. En el siguiente ejemplo, cuando el usuario hace click en la imagen se invoca el método next() del objeto window.player, y mientras el mismo botón se mantiene presionado, se invoca el método forward() del mismo objeto.

    <img onclick="player.next();" onmousedown="player.forward();" src="img/button_next.svg">
    

    El modelo de ejecución de código JavaScript sigue esta regla. El código que aparece en los elementos script es ejecutado en el mismo orden en que aparecen, durante la carga (parsing) el documento web. Cuando un elemento script termina de ejecutarse, es reemplazado por su salida, la cual es el resultado de las posibles invocaciones a document.write() que se hayan hecho. Inmediatamente el navegador continúa analizando esta salida como cualquier otro código (X)HTML, y posteriormente el resto del documento web.

    Una vez que el documento ha terminado de analizarse, todos los elementos script se habrán ejecutado y el contenido externo, como imágenes o sonidos, habrá sido cargado; el navegador dispara el primer evento: onload. El código manejador de este evento se escribe como valor del atributo onload del elemento body. El código ejecutado en este punto, puede hacer modificaciones libremente sobre la estructura del documento, ya que éste se encuentra totalmente cargado y analizado (parsed); pero no debe invocar a document.write(), ya que hará algo inesperado, como reemplazar el documento por completo.

    Después de cargar el documento, correr los elementos script y atender el evento onload, el navegador entra en la fase de manejo de eventos (event-driven phase), ejecutando el código JavaScript que se haya asociado a los atributos intrínsecos según los vaya disparando el usuario. Estos manejadores de eventos tampoco deben invocar a document.write(), ya que construirán un nuevo documento y reemplazarán el actual.

    Finalmente, cuando el usuario abandona la página, el browser dispara el evento onunload también de body, dando una oportunidad final al código JavaScript de correr. Este debe ser eficiente y silencioso, por ejemplo para hacer alguna limpieza necesaria, evitando demoras que confundan al usuario. La siguiente tabla resume los eventos intrínsecos que el autor dispone para mejorar la comunicación con el lector.

    Evento Descripción
    onclick onclick se dispara cuando el usuario hace click sobre el elemento. Si onclick retorna false, el browser no efectúa la operación por defecto asociada al elemento, por ejemplo, no seguirá el hiperlink en caso de un enlace (a), o no enviaría un formulario en caso de un botón submit.
    onmousedown, onmouseup onmousedown es disparado cuando el usuario presiona el ratón sobre un elemento y onmouseup cuando suelta el botón del ratón. Si ambos eventos ocurren sobre el mismo lugar de un elemento, se generará un onclick. La mayoría de elementos (X)HTML implementan estos dos eventos.
    onmouseover, onmouseout onmouseover se dispara mientras el cursor del ratón está sobre un elemento (X)HTML y onmouseout cuando sale de él.
    onchange El manejador onchange se dispara en los elementos que permiten ingresar texto, como input, select y textarea cuando el usuario cambia el valor desplegado del elemento y después mueve el foco hacia otro elemento.
    onload El evento onload sólo se puede asociar a body y es lanzado cuando el documento y su contenido externo, incluyendo imágenes, han sido completamente cargados.
    Eventos intrínsecos de uso común en JavaScript

    Supóngase que en una ventana del navegador el documento actual es reemplazado por otro, por ejemplo, cuando el usuario escribe un nuevo URL. El nuevo documento no puede acceder a las propiedades que el documento previo creó en la ventana, ya que el objeto window es reestablecido (reset) por el navegador. Esto implica que todas las funciones y propiedades que los scripts definan, durarán mientras el documento actual se mantenga activo.

    Consideraciones de seguridad

    El hecho de que código ajeno sea ejecutado en su máquina a través de un navegador, da oportunidad a que intenciones malignas puedan llevarse a cabo. Por esto, los navegadores simplemente no soportan ciertas funcionalidades que en otros lenguajes son naturales, como la capacidad de leer, escribir o eliminar archivos en la computadora cliente.

    Otras funcionalidades están limitadas. Por ejemplo, JavaScript puede emplear el protocolo HTTP para intercambiar información con el servidor pero no puede abrir un socket o utilizar alguna primitiva de programación de red. Algunas de estas restricciones son controlables por el usuario, ajustando la política de seguridad del navegador. De acuerdo a esta configuración su programa JavaScript:

    • Podría abrir nuevas ventanas pero sólo en respuesta a un evento del usuario (como onclik).
    • Podría cerrar ventanas, pero sólo las abiertas por sí mismo.
    • No puede ocultar el texto en la barra de estado cuando el cursor se mueve sobre un enlace.
    • No puede crear una ventana de área pequeña o muy grande, sin barra de título ni estado.
    • No puede leer ni modificar documentos cargados de servidores diferentes (same-origin policy).

    La política del mismo origen (same-origin policy) indica que un script puede acceder únicamente a contenido que tiene el mismo origen que el documento que contiene el script. Es decir, que su código no puede acceder a otro documento que tenga cargado su navegador en otra ventana obtenido de otro sitio web. También dicta que el código JavaScript puede comunicarse a través del objeto XMLHttpRequest sólo con el servidor web de donde se obtuvo el documento.

    Temporizadores y animaciones

    El objeto global window provee varios métodos y otras propiedades para manipular el navegador, ventanas, direcciones, el historial y similares.

    El método window.setTimeout(code, delay) ejecuta el código JavaScript enviado en el parámetro code, delay milisegundos después de que se hace la invocación. Retorna un identificador opaco que necesitará el programador si desea cancelar dicho temporizador con el método window.clearTimeout(). El código es invocado sólo una vez. Si se quiere que ocurra repetitivamente, el código en code debe establecer otro setTimeout() o bien usar el método setInterval(). El método timer = window.setInterval(code, period) ejecuta el código code repetitivamente en intervalos de period milisegundos, hasta que se invoque window.clearInterval(timer). Los temporizadores son muy útiles para realizar animaciones, como el siguiente reloj digital.

    
    
       Digital clock
       
    
    
    
       
    HH:MM:SS
    ]]>
    Un reloj digital animado con JavaScript. Correr este ejemplo.

    En el documento del js_clock_animation, el elemento con identificador clock de la línea 11 será utilizado para desplegar la hora local del navegador. Cuando el navegador carga el cuerpo de este documento, mostrará el div como de costumbre, aplicándole los estilos que se declararon en la parte superior. Luego encontrará un elemento script y lo ejecutará como es sabido. La invocación a setInterval() en la línea 14 le indica al navegador que debe ejecutar el código updateClock() dentro de 1 segundo, cada segundo. Al ser invocada, la función updateClock() debe cambiar el contenido del <div id="clock">...</div> con el valor actual de la hora. JavaScript requiere una referencia al objeto div que tiene el identificador clock, y la consigue invocando el método document.getElementById() que se estudiará luego. Una vez obtenida la referencia al elemento div, se cambia su contenido con la hora actual en el navegador obtenida en forma de string.

    5 pts.

    Modifique el ejemplo del reloj digital para que muestre los milisegundos (separados por un punto de los segundos), y se actualice al menos cuatro veces cada segundo.

    El js_text_animation muestra un texto moverse de izquierda a derecha, en un ancho de 320 pixeles. Esta vez no se modifica el contenido de un elemento particular, sino su posición, lo cual es responsabilidad de la presentación, por lo que el código JavaScript debe modificar dinámicamente el estilo CSS del elemento con identificador text (línea 12). El estilo de un elemento se accede en JavaScript a través de su propiedad style, la cual es un objeto cuyas propiedades tienen el mismo nombre que las propiedades CSS en notación "camelCase" (margin, marginTop, etc.), y sus valores son siempre un string. En js_text_animation, cada vez que se invoca, animateText() calcula en la variable global textLeft la nueva posición del texto (líneas 19 y 20); y en la línea 22 asigna el resultado de este cálculo a la propiedad style.left, cuyo efecto es el mismo a haber escrito #text { left: <textLeft>px; } en CSS. Para que esto funcione, el elemento #text debe "flotar" sobre el documento, lo cual se hizo en la línea 7 indicando que su posición es absoluta.

    
    
       
       Texto animado
       
    
    
    
       
    Su nombre aquí
    ]]>
    Un texto animado con JavaScript para desplazarse en vaivén de izquierda a derecha. Correr este ejemplo.
    5 pts.

    Modifique el ejemplo del texto animado para que cuando el usuario haga click en el texto, la animación se detenga, y si se vuelve a hacer click, la animación se reanude. Es obligatorio detener los temporizadores, de lo contrario, su aplicación estará mal empleando recursos de la máquina del cliente.

    Información del navegador, la ventana y la pantalla

    El objeto global window tiene varias propiedades de sólo lectura que informan el tamaño de la ventana, el tamaño del documento y la posición del navegador en el escritorio:

    Propiedad Descripción
    window.outerWidth, window.outerHeight Ancho y alto de la ventana del navegador.
    window.screenX, window.screenY Posición del navegador en el escritorio del usuario.
    window.innerWidth, window.innerHeight Tamaño del área donde el documento se despliega (también llamada área cliente o viewport. Equivale al tamaño de la ventana del navegador sin las barras de menú, herramientas, scrollbars, etc.
    window.pageXOffset, window.pageYOffset El segmento del documento actualmente desplegado en la ventana del navegador, expresado como la posición de las barras de desplazamiento (scrollbars).
    Propiedades de window para obtener la geometría del navegador
    5 pts.

    Modifique su solución del texto animado para que el texto no esté limitado a un ancho de 320 pixeles, sino que se ajuste dinámicamente al ancho del navegador.

    5 pts.

    En su solución del texto animado, reemplace el texto por una imagen, como una esfera o algún gráfico de su agrado. Haga que el gráfico se mueva tanto horizontal como verticalmente por el área de la ventana sin salirse de esta. Es decir, que el gráfico siempre es visible aún cuando se acerque a los extremos de la ventana y aunque la ventana del navegador sea redimensionada.

    El objeto window.screen provee información sobre la pantalla del usuario, por ejemplo: las dimensiones en pixeles del escritorio (screen.width y screen.height), la profundidad de color en bits por píxel (screen.pixelDepth), el área usable del escritorio sin incluir la barra de tareas (screen.availWidth y screen.availHeight). Con estas propiedades el programador puede escoger qué imágenes presentar, centrar una ventana en el escritorio o tareas similares.

    El objeto window.navigator contiene información sobre el navegador mismo, como su nombre (navigator.appName), versión (navigator.appVersion), el nombre codificado (navigator.appCodeName, el sistema operativo para el que se compiló el navegador (navigator.platform), si tiene habilitados los cookies (navigator.cookieEnabled), y la identificación que el browser envía en sus encabezados HTTP (navigator.userAgent). No es recomendable utilizar esta información para escribir funcionalidad dependiente del navegador, sino para propósitos de estadísticas o bitácoras.

    5 pts.

    Escriba un programa JavaScript que genere una o varias tablas con la información del navegador, la ventana y la pantalla citada anteriormente. No utilice document.write(), sino que su programa debe llenar un elemento section del documento (puede utilizar su propiedad innerHTML). Provea un botón "Actualizar" que cada vez que se presiona, refresca la información anterior. Recuerde aprovechar el operador for-in.

    Abrir y manipular ventanas

    JavaScript permite al autor crear nuevas ventanas de navegador en las que puede cargar documentos existentes de su servidor web o generar un nuevo documento dinámicamente. Sin embargo, el abuso que se ha cometido por publicidad y otros fines, ha obligado a los fabricantes de navegadores a restringir esta funcionalidad de acuerdo a la política de seguridad, por ejemplo, a permitir abrir ventanas sólo en respuesta a un evento del usuario.

    Una invocación al método window.open(url, name, features, replaceHistory) crea y retorna una referencia hacia una nueva ventana, y por ende, un nuevo objeto window con toda su descendencia de objetos. La nueva ventana cargará un documento si se especifica un url en el primer parámetro, de lo contrario, tendrá un documento vacío. El parámetro name le da un nombre a la ventana, si ya existe una con ese nombre simplemente se reutiliza, y en tal caso, si replaceHistory es true el historial de navegación en esa ventana es reemplazado; si es false o se omite, el nuevo documento es simplemente agregado al historial (y por tanto, el usuario podría presionar el botón "atrás" para retonar a documentos cargados previamente).

    El siguiente ejemplo crea una pequeña ventana emergente de ayuda cuando se presiona el botón, y la cierra cuando se vuelve a presionar el botón. Las ventanas se cierran con el método window.close() que cierra gráficamente la ventana pero no destruye su correspondiente objeto window.

    Ayuda
    
    ]]>
    Abrir una ventana en JavaScript. Correr este ejemplo.

    El tercer parámetro, features, de window.open() permite especificar el tamaño de la nueva ventana y otras decoraciones gráficas. Si se omite, se le dará tamaño estándar y todas las decoraciones existentes (barra de menú, barra de estado, ...). Algunas combinaciones son restringidas por razones de seguridad, por ejemplo, una ventana muy pequeña o situada en una zona no visible.

    El objeto retornado por window.open() se puede manipular como cualquier otro objeto window. El ejemplo del js_open_window_document_write, genera un documento dinámicamente en una nueva ventana, utilizando su respectivo método document.write().

    Hello world!");   // Output document content
    subdoc.close();                          // End the document
    ]]>
    Crear contenido en el documento de una ventana emergente

    Nótese que tanto document como window tinen métodos open() y close(). El método document.open(url) carga un documento a partir del url o genera uno vacío si se omite url. Invocar a document.write() es seguro en una ventana nueva ya que el documento se está cargando (parsing). El método document.close() indica al navegador que el documento ha terminado de cargarse (parsing), el cual responde deteniendo la animación de carga. Si nunca se llama a document.close(), el navegador creerá que está ante un documento incompleto.

    10 pts.

    Modifique el ejemplo de la ventana de ayuda. Cuando se presiona el botón, cree una nueva ventana con un documento vacío, es decir, el parámetro url de window.open() debe estar vacío. Haga que su programa JavaScript cree el contenido del documento dinámicamente en la nueva ventana. El contenido debe ser el mismo del window_dump, es decir, las propiedades y los valores del objeto window que contiene el script. Puede utilizar subwindow.document.write() o la propiedad subwindow.document.innerHTML.

    El objeto window tiene métodos para mover y redimensionar la ventana. Deben usarse con mucha cautela sólo en situaciones que realmente provean una mejor experiencia del usuario. De lo contrario es una pobre práctica, ya que la geometría y posición de la ventana debe ser un privilegio del usuario. Son los siguientes: window.moveTo(x, y) mueve la esquina superior izquierda a las coordenadas dadas; window.moveBy(despX, despY) mueve la ventana tantos pixeles relativos a la posición actual; window.resizeTo(width,height) y window.resizeBy(addToWidth, addToHeight) cambian el tamaño de la ventana.

    El método window.focus() hace que la ventana sea la activa, y window.blur() hace que renuncie a serlo. Es común llamar a focus() después de hacer un window.open(), ya que las ventanas recién creadas no lo tienen por defecto.

    Es común querer abrir un documento existente en una nueva ventana y luego hacer que ésta se desplace a diferentes fragmentos del documento dinámicamente. Si los fragmentos están identificados con el atributo id (como en <div id="frag1">...</div>) o con anclas (<a name="frag2">...</a>), se puede cambiar la ubicación del documento con un una de las siguientes instrucciones:

    var subwindow = window.open('help.html', 'help_window');
    subwindow.location.hash = "#frag1";    // crea una entrada en el History de subwindow
    subwindow.location.replace("#frag1");  // no crea una entrada en el History de subwindow
    

    Ubicación del documento

    La propiedad window.location de una ventana representa el URL del documento que está desplegado en dicha ventana. Es un objeto que tiene las siguientes propiedades:

    Propiedad Descripción
    href Es un string con el texto completo del URL.
    protocol, host, pathname, search Representan partes individuales del URL.
    reload() Recarga la página como si el usuario lo hiciera en el navegador.
    replace(url) Reemplaza el documento actual en la ventana por uno nuevo cuyo URL es dado por parámetro. No genera una entrada en el historial de la ventana, de modo que el usuario no tiene forma de regresar al documento previo.
    Propiedades del objeto window.location

    Al objeto mismo window.location se le puede asignar un string, de la forma window.location = url, que causa el mismo efecto que llamar su método window.location.replace(url), con la diferencia de que se agrega una entrada al historial de navegación de la ventana, de tal forma que el usuario puede retornar al documento previo.

    Cuadros de diálogo

    El objeto window provee tres métodos para desplegar cuadros de diálogo. window.alert(msg) despliega un mensaje y espera que el usuario lo acepte. window.confirm(msg) presenta un mensaje y solicita al usuario que decida si lo acepta o cancela, lo cual se retorna como un boolean. window.prompt(msg, defaultValue) presenta el mensaje msg y espera que el usuario ingrese un string el cual es retornado o null si cancela.

    Deben usarse con moderación, o mejor aún, nunca. La mayoría de usuarios se sentirá molesta al experimentarlos y recuerde que ellos tienen el poder forzar el navegador a cerrarse, deshabilitarle JavaScript o nunca volver a su sitio. Así que estos métodos son de ligera utilidad para el programador durante el proceso de desarrollo. Hace varios años, el navegador reportaba errores de JavaScript utilizando diálogos, lo cual era impertinente: le reclamaba al visitante errores que no eran de él. Los navegadores han cambiado a ocultar los errores y el programador debe buscarlos en alguna bitácora, consola o similar.

    El objeto document

    En esencia un navegador es un programa que despliega un documento web en una ventana. La sección anterior explica cómo manipular con JavaScript esa ventana. Esta sección explica cómo manipular dinámicamente el documento cargado en ella.

    Hasta el momento las propiedades más utilizadas del objeto document en este material han sido los métodos write() y writeln(). Ambos reciben una cantidad arbitraria de parámetros y los escriben en el documento para que sean analizados (parsed) por el navegador posteriormente. Por eso, estos métodos sólo se deben invocar mientras se está cargando (parsing) el documento HTML y no XHTML, ya que XML prohíbe este comportamiento. Como es de esperar, writeln() después de imprimir todos sus parámetros, agrega un carácter de cambio de línea. Otras propiedades útiles del objeto document son presentadas en la js_window_document, y con ellas se escribe, a modo de ejemplo, un pie de página dinámico en el js_dynamic_footer.

    Propiedad Descripción
    document.title Accede al título del documento que se encuentra en el encabezado del mismo.
    document.lastModified Contiene un string con la fecha de última modificación del documento.
    document.URL Es un string con el URL completo de donde se obtuvo el documento.
    document.referrer Es un string con el URL del recurso a través del cual el usuario llegó al documento actual.
    document.domain Indica el dominio al cual pertenece el documento, y es usado por la política del mismo origen (same origin policy).
    Propiedades del objeto window.document

    El modelo de objetos del documento (DOM)

    Cuando el navegador carga un documento (X)HTML crea un objeto JavaScript por cada elemento, comentario, o trozo de texto que encuentra en el archivo. Estos objetos se asocian entre sí en una jerarquía llamada modelo de objetos del documento (DOM: Document Object Model), la cual es accesible y modificable a través de JavaScript. Es decir, el DOM es una representación interna del documento utilizando objetos. Un cambio en el DOM se refleja de inmediato en el navegador, lo que permite al autor ajustar el documento dinámicamente para mejorar la comunicación con sus lectores. Es quizá la funcionalidad más importante que un autor busca de JavaScript.

    Los objetos en la jerarquía del DOM reciben el nombre genérico de nodos, construidos con la pseudoclase Node. El nodo raíz es siempre el documento, el cual contiene nodos opcionales (declaración XML, declaración de tipo de documento, comentarios) y el nodo del elemento html, llamado elemento documento (document element). Este último tiene dos hijos: el nodo del encabezado (head) y el nodo del cuerpo del documento (body). Los hijos del nodo body varían de acuerdo al documento. Por ejemplo, la fig_node_tree muestra un documento sencillo y el árbol de nodos que el navegador genera a partir de él.

    Un documento y su árbol de nodos

    La pseudoclase Node

    Las relaciones jerárquicas entre nodos implican que un nodo puede tener cero o más nodos hijos (child nodes), nodos hermanos (siblings), un nodo padre (parent), nodos descendientes y nodos ancestros. Estas relaciones se implementan como propiedades de la pseudoclase Node. Dado un nodo se puede acceder a los otros nodos con los que está relacionado. Estas y otras propiedades se resumen en la siguiente tabla.

    Propiedad Descripción
    Node.childNodes[] Un arreglo que contiene los hijos del nodo actual en el mismo orden en que fueron definidos en el documento. Si el nodo actual no tiene hijos, este arreglo estará vacío.
    Node.firstChild El primer nodo hijo del nodo actual, o null si no tiene hijos.
    Node.lastChild El último nodo hijo del nodo actual, o null si no tiene hijos.
    Node.nextSibling El nodo hermano que continúa al actual o null si es el último hijo del nodo padre (parentNode). Equivale al nodo que sigue al actual en el arreglo parentNode.childNodes[].
    Node.previousSibling El nodo hermano que precede al actual o null si es el primer hijo del nodo padre (parentNode). Equivale al nodo que precede al actual en el arreglo parentNode.childNodes[].
    Node.parentNode El nodo padre o contenedor del nodo actual. Es null en el caso del nodo raíz, nodos que han sido separados (removidos) del documento, o nodos creados libremente que aún no han sido insertados en el documento.
    Node.nodeType Un entero que indica el tipo de nodo, por ejemplo: elemento (1), texto (3), comentarios (8). Ver xml_dom_node_types.
    Node.nodeName Un string con un posible nombre para el nodo. Si el nodo es un elemento se utiliza el nombre del elemento ("body", "div", "p", etc.). Para los demás tipos de nodos se utiliza un texto generado de concatenar el símbolo de número con el tipo de nodo, por ejemplo "#text", "#comment", "#document", etc.
    Node.nodeValue Si el nodo es de texto (nodeType == 3) o comentario (nodeType == 8), esta propiedad tendrá un string con dicho texto; de lo contrario el valor null.
    Node.textContent Contiene el texto si el nodo es de texto (nodeType == 3) o comentario (nodeType == 8), en la misma forma que nodeValue. Para un elemento (nodeType == 1) contiene el texto resultado de concatenar todos los nodos de texto descendientes.
    Node.ownerDocument Una referencia hacia el documento que contiene este nodo. Sirve, por ejemplo, para saber si dos nodos son parte del mismo documento.
    Propiedades de la pseudoclase Node del XML DOM

    El código del js_print_nodes recorre recursivamente todos los nodos del documento e imprime algunas de las propiedades heredadas de la pseudoclase Node. La cantidad de nodos está fuertemente influenciada por el espacio en blanco que se utilice para indentar el marcado, ya que el parser lo interpretará como datos de carácter y los almacenará en nodos de texto. [Para este ejemplo, la impresión con document.write() debe hacerse en un documento distinto, ya que si se hace sobre el mismo, generará una recursión infinita].

    Una función recursiva que recorre el árbol de nodos del documento e imprime propiedades heredadas de la pseudoclase Node. Correr ejemplo completo sin espacios en blanco, con indentación en XHTML, o con indentación en HTML.

    El ejemplo del js_print_nodes permite observar que los navegadores agregan a la jerarquía nodos #text por el texto en blanco que aparece entre los elementos del documento, debidos a la indentación del autor. Además los nombres de los elementos en XHTML se mantienen en minúsculas mientras que en HTML son convertidos a mayúsculas.

    Además de las propiedades para acceder a nodos relacionados de la xml_dom_node_properties, la pseudoclase Node provee métodos para modificarlos, los cuales se listan en la xml_dom_node_methods.

    Propiedad Descripción
    Node.appendChild(newChild) Agrega el nodo newChild al final del arreglo childNodes[]. El cambio se ve reflejado de inmediato en el documento cargado en el navegador. Si newChild ya forma parte del documento, será removido de su posición actual y reinsertado en la nueva posición.
    Node.insertBefore(newChild, referenceChild) Inserta el nodo newChild en el arreglo childNodes[] justo antes que el nodo referenceChild, el cual obligatoriamente debe existir y ser hijo del nodo al cual se le invocó este método. Si referenceChild es null, newChild se insertará al final. Si newChild ya es parte del documento, será removido de su posición actual y reinsertado en la nueva posición.
    Node.replaceChild(newChild, oldChild) Reemplaza oldChild con newChild. Si newChild ya es parte del documento, será removido de su posición actual y reinsertado en la nueva posición. oldChild es separado del documento y el cambio se refleja en el navegador. El objeto oldChild no es eliminado de la memoria, sino que sigue vigente y puede ser reinsertado en el documento posteriormente.
    Node.removeChild(child) Quita al nodo child del arreglo childNodes[] y del documento (el cambio se refleja visualmente en el navegador). El objeto nodo child no es eliminado de la memoria, y puede ser reinsertado en el documento posteriormente.
    Node.cloneNode(recursively) Retorna una copia del nodo al cual este método es invocado. No clona los hijos y descendientes del nodo a menos que se envíe true en el parámetro recursively. El nodo retornado no es parte del documento, por lo que debe ser insertado posteriormente si se quiere que sea visible al lector.
    Node.isEqualNode(other) Retorna true si el nodo es idéntico al enviado por parámetro, es decir, tienen los mismos valores para sus propiedades de datos (como el nombre y tipo), y así recursivamente para todos sus hijos.
    Métodos de la pseudoclase Node del XML DOM

    Los métodos de la xml_dom_node_methods permiten mover nodos existentes de un lugar a otro, o clonarlos. El siguiente ejemplo ordena los nodos existentes de una lista alfabéticamente.

    
        

    Reubicar nodos en el documento

    1. Los
    2. 12
    3. elementos
    4. de
    5. esta
    6. lista
    7. serán
    8. ordenados
    9. alfabéticamente
    10. sin contar
    11. Mayúsculas
    12. Incluyendo este párrafo.

    ]]>
    Reubicar nodos en el documento. Correr este ejemplo.

    Nótese que en el ejemplo anterior se construyó un arreglo temporal (llamado elements[]), el cual se llenará únicamente con los elementos que deben ser ordenados, y luego serán reinsertados en el documento. ¿Por qué no se hizo este cambio directamente en list_element.childNodes[]? Hay varias razones. La más importante es porque childNodes[] no es realmente un arreglo, sino un objeto que se comporta como tal llamado objeto tipo arreglo (array-like objects), y su propósito es proveer un arreglo controlado, casi de sólo lectura. Por ejemplo, el programador no puede invocar a list_element.childNodes.sort() porque el objeto tipo arreglo no implementa este método.

    La segunda razón es que el arreglo list_element.childNodes[] muy probablemente contiene otros hijos que no son de importancia ordenar, como nodos de texto (surgidos de los espacios en blanco usados para indentar el código fuente) o comentarios. Por esto, es una práctica común construir un nuevo arreglo temporal escogiendo los nodos que realmente se quieren modificar, afectar al arreglo temporal, y cuando el procesamiento esté listo, actualizar el documento. Esto reduce además un desagradable efecto de parpadeo (flickering) que confunde al lector al ver partes del documento cambiar arbitrariamente.

    5 pts.

    Modifique el ejemplo de ordenar elementos de la lista (js_sort_list) para que cuando se presiona el botón, se alterne el orden de los elementos entre ascendente y descendente en la lista.

    Creación de nodos en el documento

    Cuando se debe crear un nuevo nodo en el documento, no se debe hacer con el operador new, de la forma new Node(), sino utilizar los métodos document.createXxx(data) de la pseudoclase Document, como los listados en la js_document_create_node_methods. Todos ellos crean un nuevo nodo el cual está asociado al documento, pero no es visible. Cuando el programador haya terminado de procesarlo (por ejemplo, inicializarlo), deberá insertarlo en la jerarquía de nodos del documento en el punto que quiere que sea visible al lector, usando alguno de los métodos de la pseudoclase Node como appendChild(), insertBefore() o replaceChild() listados en la xml_dom_node_methods. El ejemplo del js_append_child_node inserta nodos dinámicamente en una lista ordenada.

    Método Descripción
    document.createElement(name) Crea un nodo elemento del nombre dado. Si el elemento tiene texto en el contenido, se debe crear al menos un nodo de texto con createTextNode() y agregarlo como hijo.
    document.createTextNode(text) Crea un nodo de texto con el texto dado por parámetro.
    document.createComment(text) Crea un nodo comentario con el texto dado por parámetro.
    att = document.createAttribute(name) Crea un nodo atributo con el nombre dado y lo retorna. El valor del atributo se puede asignar luego con att.value = 'valor';. El atributo se puede aplicar a uno o más elementos con el método elemento.setAttributeNode(att).
    document.createCDATASection(text) Crea una sección CDATA con el texto dado por parámetro. Sólo está disponible en XML DOM, es decir, en jerarquías de objetos generadas a partir de documentos XHTML y no HTML.
    Algunos métodos de Document para crear nodos
    
        

    Crear e insertar nodos en el documento

    1. Presione el botón para agregarle elementos a la lista
    ]]>
    Crear e insertar nodos en el documento. Correr este ejemplo.

    La línea 11 del js_append_child_node crea un objeto nodo en el documento, pero que no se ha indicado en qué punto del documento debe aparecer, por lo que no es visible en la ventana del navegador. Esto ocurre hasta cuando el nuevo nodo es insertado en la jerarquía de nodos del documento (DOM), en este caso en la línea 14 como el último nodo hijo de la lista con identificador dynamic_list declarada entre las líneas 4 a 6.

    Cada vez que el usuario presiona el botón en el js_append_child_node se construye un elemento li vacío. Para especificar el texto que va en su contenido hay dos formas: con la propiedad innerHTML o creando un nodo de texto (línea 12) y agregarlo como hijo del recién creado li, como se hizo en la línea 13 del js_append_child_node. La propiedad innerHTML sólo está disponible en HTML, mientras que crear un nodo y agregarlo al documento (DOM) es el método universal.

    10 pts.

    Escriba un documento XHTML con un poema, y un botón que al ser presionado invierte el orden de las estrofas (no de los versos), utilizando los métodos de la pseudoclase Node. Imprima los números de las estrofas y resáltelos con estilos CSS para que el cambio sea visualmente evidente.

    XML DOM y HTML DOM

    Los nodos pueden ser de distinta naturaleza: elementos, atributos, comentarios, texto, etc. La propiedad Node.nodeType indica el tipo de un nodo particular; es un entero que puede tomar el valor de una de las siguientes constantes:

    Valor Constante Pseudoclase XML Pseudoclase HTML
    1 Node.ELEMENT_NODE Element HTMLElement
    2 Node.ATTRIBUTE_NODE Attr
    3 Node.TEXT_NODE Text
    4 Node.CDATA_SECTION_NODE CharacterData
    5 Node.ENTITY_REFERENCE_NODE EntityReference
    6 Node.ENTITY_NODE Entity
    7 Node.PROCESSING_INSTRUCTION_NODE --
    8 Node.COMMENT_NODE Comment
    9 Node.DOCUMENT_NODE Document
    10 Node.DOCUMENT_TYPE_NODE --
    11 Node.DOCUMENT_FRAGMENT_NODE DocumentFragment
    12 Node.NOTATION_NODE --
    Tipos de Node del XML DOM

    El js_element_count muestra una función que recorre recursivamente todos los nodos del documento contando cuántos de ellos son elementos. Sugerencia: verifique si el conteo es correcto: cuente los elementos manualmente en el código fuente que genera la página o al inspeccionar el resultado.

    
    
    
       
       Conteo de elementos
       
    
    
    
       

    Conteo de elementos

    El número de elementos presentes en este documento es:

    ]]>
    Una función recursiva que recorre el árbol de nodos del documento y retorna cuántos de ellos son de tipo elemento. Correr este ejemplo.
    10 pts.

    Escriba en un archivo .js, una función que recorre el documento en que fue invocada y retorna un string con el texto de todos los comentarios. Imprima este string en un elemento pre al final del documento. El elemento pre y su contenido debe crearlo desde JavaScript usando el DOM. Puede descargar una página que guste de la web que tenga comentarios y alterarla para incluir su script al final del cuerpo.

    15 pts.

    Escriba una función en un archivo doc_statistics.js que al ser invocada crea un nuevo documento en una ventana emergente con estadísticas del documento donde se invocó la función. El contenido de la ventana emergente es una tabla (X)HTML con las columnas: número de línea (#), el nombre del elemento (Elemento), la cantidad de veces en que aparece en el documento original (Ocurrencias), y el porcentaje de ocurrencia de dicho elemento en el documento (Porcentaje). En las filas de la tabla debe aparecer cada uno de los distintos elementos que conforman el documento, como se ve abajo. Al final agregre como pie de tabla una fila con los totales. Descargue de la web una página de su interés y modifíquela para ejecutar su script al final del documento. Note que su función sólo debe considerar elementos y no nodos de otro tipo.

    #ElementoOcurrenciasPorcentaje
    1html10.1%
    2head10.1%
    ............
    45img9510%
    Total581100%

    Por cada tipo de nodo hay una pseudoclase hija de Node, que aparece en la tercera columna de la xml_dom_node_types. Por ejemplo, cuando el navegador carga el documento y encuentra un comentario, no construye un objeto Node puro, sino que construye un objeto de la pseudoclase Comment, la cual es hija de Node; y, de igual forma, cuando encuentra un párrafo creará un objeto Element que es también hijo de Node. Esta jerarquía de pseudoclases se ilustra la fig_node_inheritance.

    Herencia de pseudoclases del XML DOM y HTML DOM

    Cada subclase agrega propiedades a la pseudoclase Node. Por ejemplo, la pseudoclase Element define las siguientes propiedades útiles:

    Propiedad Descripción
    Element.tagName String con el nombre del elemento, por ejemplo: "body", "p", etc. Equivale a Node.nodeName.
    Element.attributes[] Accede a los atributos del elemento en el orden en que fueron definidos.
    Element.getAttribute(name) Retorna el valor del atributo name como un string, o null si el elemento no tiene este atributo.
    Element.setAttribute(name, value) Asigna el string value al atributo con nombre name del elemento. Si el atributo no existe, lo crea.
    Element.removeAttribute(name) Elimina el atributo con nombre name del elemento. No produce ningún error si el atributo no existe.
    Element.getAttributeNode(name) Retorna un objeto de tipo Attr que permite por ejemplo saber si el atributo fue definido o se está usando su valor por defecto.
    Element.id El valor del atributo id="value", si fue definido para este elemento.
    Element.className El valor del atributo class="value", si fue definido para este elemento.
    Element.children[] Un arreglo que contiene únicamente los elementos hijos de este elemento, en lugar de todos los nodos como ocurre con el arreglo childNodes heredado de Node. Quizá childElements hubiese sido un nombre más apropiado.
    Element.childElementCount La cantidad de elementos hijos. Equivale a children.length. Nótese que no es la cantidad de nodos hijos.
    Element.innerHTML Un string que con el marcado HTML o XML que representa el contenido del elemento. Si se asigna otro string, será "parseado" por el navegador, el resultado reemplazará a los nodos hijos del elemento, y el efecto será inmediatamente visible en el documento.
    Element.outerHTML Un string que con el marcado HTML o XML que representa el elemento mismo más su contenido. Si se asigna otro string, será "parseado" por el navegador, el resultado reemplazará al elemento y sus nodos hijos. El efecto será inmediatamente visible en el documento.
    Element.style Un objeto CSSStyleDeclaration con los estilos definidos en el documento para este elemento. Se puede modificar dinámicamente. No contiene los estilos computados.
    Propiedades de Element en el XML DOM

    El objeto Attr representa un atributo de un elemento particular, aunque casi nunca se utiliza, ya que el objeto Element tiene propiedades y métodos para manipular sus atributos. Quizá su propiedad más útil es Attr.specified que permite determinar si el valor del atributo fue explícitamente escrito en el documento, sino se está tomando el valor por omisión.

    Los navegadores realmente implementan dos modelos de objetos del documento (DOM), uno para XML y otro especializado para HTML (fig_node_inheritance). Dado que JavaScript utiliza XML para transferir datos entre el servidor y el cliente, requiere poder manipular estos documentos a través de una jerarquía de objetos. El DOM que se ha presentado hasta el momento se llama XML DOM, compuesto de objetos bastante genéricos como lo es también XML.

    Sin embargo, el navegador provee un conjunto de pseudoclases especializadas para HTML, que componen lo que se llama HTML DOM y que heredan de las pseudoclases presentes en el XML DOM. Algunas de estas pseudoclases se presentan en verde en la fig_node_inheritance. El principal distintivo del HTML DOM es que define la pseudoclase HTMLElement para todos los elementos que sólo tienen los atributos comunes (em, span, dt, etc.) y una pseudoclase HTMLXxxElement para cada elemento Xxx que tiene atributos propios, como HTMLDocument, body (HTMLBodyElement), p (HTMLParagraphElement), ul (HTMLUListElement), etc.

    Las pseudoclases HTMLXxxElement definen propiedades homónimas a los atributos (X)HTML que son de lectura y escritura. Por ejemplo HTMLImageElement define la propiedad string src que puede cambiarse dinámicamente con JavaScript y su efecto es inmediato en el navegador. Los nombres son homónimos a XHTML, en minúsculas, excepto el atributo class="" que en JavaScript se renombra className debido a que class es una palabra reservada. El tipo de datos de cada atributo es el más adecuado, por ejemplo, HTMLImageElement.src es un string, HTMLImageElement.width es un entero y HTMLBodyElement.onload es una función de JavaScript.

    El autor puede utilizar estos atributos definidos por las pseudoclases HTMLXxxElement directamente con el operador punto o el operador corchetes, si está seguro de que el objeto es del tipo xxx adecuado. También puede emplear los métodos getAttribute(name) y setAttributeValue(name, value) heredados de Element en el XML DOM; que a diferencia de los atributos de HTMLXxxElement, sólo retornan y reciben valores string.

    5 pts. Reimplemente el ejercicio 5.22 (documento aleatorio) usando la jerarquía de Node, en lugar de usar una jerarquía de clases propias. Provea una función que al ser invocada obtiene una referencia al elemento body y construye un documento aleatorio.

    Localizar elementos en el documento

    De todos los tipos de nodos el elemento es incuestionablemente el más importante para el autor, tanto que el objeto Document del XML DOM y HTMLDocument del HTML DOM, proveen varios métodos útiles para localizar elementos por algún criterio. Estos se resumen en la siguiente tabla.

    Método Descripción
    document.getElementById(id) Localiza el único elemento que tiene el identificador id en su atributo homónimo. Retorna el objeto HTMLElement que tiene este identificador o null si ningún elemento en el documento está identificado con el string id.
    document.getElementsByTagName(tagName), element.getElementsByTagName(tagName) Selecciona todos los elementos en el documento que fueron creados con la etiqueta tagName, y los retorna en un objeto tipo arreglo que tiene el mismo orden en que aparecen en el documento. Por ejemplo, document.getElementsByTagName('table') retorna un objeto tipo arreglo con todas las tablas del documento. La búsqueda se hace sin importar mayúsculas y minúsculas por compatibilidad con HTML, y si se envía un asterisco '*' en tagName retorna todos los elementos del documento.
    document.getElementsByClassName(classValue), element.getElementsByClassName(classValue) El atributo class="c1 c2 ... cN" indica que el elemento pertenece a una o más clases separadas por espacios (y no por comas). Una clase es una agrupación de elementos que comparten características comunes; por ejemplo, una clase ejercicio podría usarse para distinguir todos los párrafos que representen ejercicios en un libro (<p class="ejercicio">...</p>) y aplicarles estilos diferenciados. En JavaScript se puede tener un objeto tipo arreglo con todos los elementos del documento que pertenecen a una o a varias clases con el método document.getElementsByClassName(), que recibe un string con la lista de clases separadas por espacios sin importar el orden.
    document.querySelectorAll(selector), element.querySelectorAll(selector) CSS tiene una notación estándar para seleccionar elementos a los cuales aplicarles reglas de estilo; por ejemplo, el selector ".ejemplo" recupera todos los elementos que tienen el atributo class="ejemplo"; el selector "#cap01 a[target='blank']" selecciona a todos los enlaces que tienen el atributo <a target="_blank"> y que son descendientes del elemento identificado con id="cap01". En JavaScript el método document.querySelectorAll(selector) retorna un objeto tipo arreglo con todos los elementos que satisfacen el selector enviado por parámetro, el cual sigue la misma notación de los selectores de CSS.
    document.querySelector(selector), element.querySelector(selector) Hace lo mismo que querySelectorAll(), pero sólo regresa el primer elemento que satisface el selector en lugar de una lista de elementos.
    document.getElementsByName(name) El atributo name sirve para identificar varios objetos relacionados, especialmente una familia de botones de radio (radio buttons). Ya que varios elementos pueden tener el mismo nombre, este método retorna un objeto tipo arreglo de elementos que comparten dicho nombre. A diferencia de los demás, éste método sólo está disponible en HTML DOM y no en XHTML DOM.
    Métodos de HTMLDocument para localizar elementos

    Si los métodos de la htmldocument_search_element se invocan con el objeto document, buscarán elementos en el documento completo, pero a veces, es útil buscarlos sólo en un trozo del documento. La pseudoclase Element también define sus propias versiones de dichos métodos (a excepción de getElementById() y getElementsByName()), y hacen lo mismo que sus contrapartes de Document, con la diferencia de que en lugar de buscar en todo el documento, buscan únicamente en el subárbol del elemento al cual el método es invocado.

    Por conveniencia la pseudoclase HTMLDocument define las propiedades document.head, document.body y document.documentElement, que hacen referencia a sus respectivos elementos, lo cual ahorra al programador el tener que localizarlos con las funciones de búsqueda del documento:

    document.head == document.getElementsByTagName('head')[0];
    document.body == document.getElementsByTagName('body')[0];
    document.documentElement == document.getElementsByTagName('html')[0];
    

    Ejemplo: Preguntas frecuentes

    El ejemplo del js_faq_example_xhtml muestra una sencilla sección de "Preguntas frecuentes". Inicialmente se presentan sólo las preguntas y no las respuestas, lo que permite al visitante recorrerlas rápidamente. Una vez que encuentre la pregunta que le intriga, puede hacer clic en ella para desplegar su respuesta.

    Este ejemplo sirve para ilustrar varios conceptos comunes de la programación en JavaScript. De acuerdo a la filosofía Unobtrusive JavaScript, el uso de este lenguaje debería ser un aditamento opcional que mejora la comunicación con el usuario. En este caso, si el visitante tiene desactivado el intérprete de JavaScript en su navegador, podrá acceder a la sección de "Preguntas frecuentes" completa. Es decir, JavaScript no debe ser un requisito indispensable para que un documento web sea funcional; pero si está disponible, JavaScript sólo debería ser usado para mejorar la experiencia del usuario con el sitio web.

    El documento web no debe especificar comportamiento, sino que éste siempre debe estar en un archivo externo, el cual se liga con el atributo src del elemento script como se hizo en la línea 15 de faq.xhtml en el js_faq_example_xhtml. Si los scripts van a ejecutarse hasta después de que el documento haya sido cargado por completo, es buena práctica incluirlos hasta final del documento web. De esta forma el contenido se cargará tan rápido como sea posible, sin ser retrasado por código JavaScript que aún no va a correr.

    Al procesar la línea 15 de faq.xhtml en el js_faq_example_xhtml, el navegador deberá esperar a descargar el archivo faq.js completo. Cuando esto ocurre, encontrará que sólo tiene 1 instrucción ejecutable (línea 2), las demás sólo declaran funciones como propiedades del objeto window. La línea 2 es la única que el navegador ejecuta en esta etapa y básicamente le indica al navegador que debe invocar la función initFaq() inmediatamente después de que haya terminado de cargar el documento web por completo (esto se estudiará más adelante). El navegador registra el evento y continúa analizando (parsing) el resto del documento web faq.xhtml.

    Una vez que el navegador ha terminado de analizar el documento faq.xhtml, dispara el evento onload y ejecuta todos los manejadores que se hayan asociado a dicho evento. En este caso, la única función asociada es initFaq de las líneas 5 a 44, la cual se invocará con el fin de inicializar la lista de preguntas y respuestas. Inicializar esta lista significa ocultar las respuestas y hacer que los textos de las preguntas reaccionen al clic del ratón con el fin de mostrar u ocultar alternadamente dichas respuestas. Adicionalmente crea un par de botones: "Mostrar todo" para los visitantes que quieran leer o imprimir todas las preguntas, y el botón "Ocultar todo" para quienes cambien de opinión.

    
    
    1. ¿Texto de la pregunta 1?

      Respuesta de la pregunta 1.

    2. ¿Texto de la pregunta 2?

      Respuesta de la pregunta 2.

      Esta respuesta ocupa varios párrafos.

    Manejo de eventos del DOM level 2

    ]]>
    Un archivo .js que al ser incluido por un documento, agrega dos manejadores de eventos. Correr este ejemplo.

    El método Element.addEventListener() es parte de lo que se conoce como DOM Level 2, el cual establece que cuando un evento ocurre en un elemento, éste puede antenderlo o alguno de sus elementos padres, es decir, el evento se propaga por la jerarquía de elementos y puede ser atendido en una de las siguientes tres fases:

    • Capturing phase. El evento recorre desde el documento hasta el elemento esperando ser atendido. Es decir, un elemento padre del elemento que generó el evento puede atenderlo.
    • At target phase. El elemento que generó el evento tiene oportunidad de atenderlo.
    • Bubbling phase. El evento retorna desde el elemento hacia el documento.

    El tercer parámetro del método element.addEventListener(event, function, fase) controla la fase en la que será atendido del evento. Si es true se atenderá en la fase de captura, si es false en la fase objetivo o fase de ascenso (bubbling).

    Otra ventaja de usar addEventListener() en lugar de asignar manejadores de eventos directamente a los atributos intrínsecos (llamado DOM Level 0), es que una misma función puede menejar eventos generados en distintos lugares del documento. Por ejemplo, si quiere hacer algo cuando el cursor del ratón pase sobre cualquier párrafo del documento, en el DOM Level 0 tendría que recorrer todos los elementos p y asignarles la función manejadora; mientras que en el DOM Level 2, simplemente registra la función en el elemento document.body con addEventListener().

    Tanto en DOM Level 0 como DOM Level 1, la función que se asigna al atributo intrínseco o que se pasa en el segundo parámetro de element.addEventListener(event, function, fase) se convierte en una propiedad del elemento al cual se hizo la asignación o invocación. Es decir, esa función se convierte en un método del elemento. Cuando éste se invoca por el navegador, se hará como una invocación a un método normal del elemento, así la palabra reservada this hará referencia a dicho elemento, por lo que se puede usar dentro del código del manejador. El siguiente ejemplo muestra una mínima implementación de un juego de estallar una bolsa de burbujas. Nótese cómo la función popBubble es asignada a cada imagen de burbuja al evento onclick, lo que la convierte en un método, el cual utiliza el parámetro this para cambiar el origen de la imagen presionada por una burbuja estallada.

    
    
    
       
       Estallar burbujas