Universidad de Costa Rica
Escuela de Ciencias de la Computación e Informática
CI-2413 Desarrollo de aplicaciones web
Profesor: Jeisson Hidalgo Céspedes
11-Octubre-2011

Laboratorio 07

Este laboratorio continúa el aprendizaje autodidáctico del lenguage JavaScript. Algunos ejercicios requieren el trabajo hecho por el estudiante en el Laboratorio 6.

Comportamiento con JavaScript

Generalidades

Sección presente en el laboratorio 6.

Tipos de datos y variables

Sección presente en el laboratorio 6.

Expresiones

Operadores

Los operadores aritméticos de resta (-), multiplicación (*), división (/) y módulo (%) sólo trabajan con números. Si se usan con 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 (===) 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
"0" === false  // produce false

"037.50" ==  37.5 // produce true
"037.50" === 037.50 // produce false

Ejercicio 0. Pruebe todas las expresiones que aparecen en los ejemplos de esta sección en el intérprete de JavaScript de su navegador. Por ejemplo, Chrome provee una "JavaScript Console" (Ctrl+Alt+J), donde puede introducir sentencias que serán evaluadas "en vivo". Sugerencia cognitiva: escriba cada expresión manualmente, en lugar de copiarla y pegarla.

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 expresiones lógicas.

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: &&, || y !. Pero guardan una diferencia. Los operandos se convierten a Boolean temporalmente para hacer la evaluación del operador, pero el resultado del operador no es Boolean, sino del tipo de datos del operando izquierdo. 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 || 500;

El operador ternario ?: funciona igual que en C. Los operadores de bits (bitwise operators): and (&), or (|), xor (^), not (~), shift left (<<), shift right con signo (>>) 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.

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 tanta 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

function Point(x, y) { this.x = x; this.y = y; }  // Una función constructora
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 delete no se trata de eliminar memoria dinámica como ocurre en C++, lo cual se hace en JavaScript con el 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

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.

function transfer(file, method)
{
   switch(method)
   {
      case 'none':
         break;

      case 'ftp':
         return transfer_ftp(file);

      case 'rsync':
      default:
         return transfer_rsync(file);
   }
}

Los ciclos for, while y do/while tienen la misma sintaxis de C. Por ejemplo:

// Llenar un arreglo con 20 factoriales: del 0 al 19
var factorials = new Array(20);
for(var i = 0; i < factorials.length; ++i)
   factorials[i] = i ? i * factorials[i - 1] : 1;

// Imprimir el arreglo en el documento
for(var i = 0; i < factorials.length; ++i)
   document.write(i, '! = ', factorials[i], '<br>');
Ejemplo del ciclo for en JavaScript. Correr este ejemplo.

Ejercicio 1. 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 agrega un tipo de ciclo más, el ciclo for/in, cuya sintaxis es:

for (property_name in object)
   statement;

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 la propiedad, es decir, un string. Por ejemplo:

for (var prop_name in my_object)
   document.write("name: " + prop_name + "; value: " + my_object[prop_name], "<br/>");

Ejercicio 2. Utilice un ciclo for/in para imprimir recursivamente los activos del inventario que hizo en el ejercicio 15 del laboratorio 6.

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.

Ejercicio 3. 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.

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().

Ejercicio 4. Provea una función (o un juego de funciones) que reciba por parámetro un valor de cualquier tipo de datos, y lo escriba en el documento utilizando la misma notación de objetos de JavaScript (JSON). Note que esta función (o funciones) debe ser recursiva. Idee algún mecanismo para mantener una indentación impecable en el resultado.

Ejercicio 5. Invoque a su función genArray() para generar un arreglo de elementos aleatorios, e imprímalo en el documento utilizando la función que escribió en el ejercicio anterior. Debe ocurrir que cada vez que se refresque el documento, el navegador genere una estructura de datos distinta, con niveles de composición aleatorios.

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.

Propiedades de Object

Cuando se crea un objeto 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. También debería ser sobrescrito para clases descencientes.

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 siguiente tabla muestra una lista de métodos heredados de la pseudoclase Array.

Métodos de Array
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]

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. En JavaScript es fácil, incluso, implementar una función que reciba una cantidad arbitraria de parámetros.

Cada vez que se invoca una función, JavaScript llena un objeto-arreglo 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 arguments[i] accede al argumento i + 1 en la lista. El siguiente es un ejemplo de una función tradicional:

// Sea una funcion que espera dos parametros, ignora los demas
function min(a,b) { return a < b ? a : b; }

// En JavaScript es completamente valido invocarla con menos o mas parametros
min(3, 0.07, -0.5);   // retorna 0.07

// Al hacer la invocacion anterior en min() ocurre lo siguiente:
//   a == 3
//   b == 0.07
//   arguments[0] == 3
//   arguments[1] == 0.07
//   arguments[2] == -0.5
//   arguments.length == 3
//   min.length == 2   // la cantidad de argumentos declarados en la funcion

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. Por ejemplo:

// Esta version de min solo trabaja con dos parametros
function min(a,b)
{
   // Si el numero dado de argumentos es diferente del esperado, lance un error
   if ( arguments.length != min.length )
      throw new Error('min(): expected ' + min.length + ' arguments, sent ' + arguments.length);

   // Solo dos parametros fueron provistos como se esperaba
   return a < b ? a : b;
}

try
{
   min();              // Lanza excepcion
   min(3);             // Lanza excepcion
   min(3, 0.07);       // Retorna 0.07
   min(3, 0.07, -0.5); // Lanza excepcion
}
catch(exc)
{
   document.write(exc);
}
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 con un número arbitrario de parámetros, como se ve a continuación.

function min()
{
   var result = arguments.length ? Number.MAX_VALUE : undefined;
   for (var i = 0; i < arguments.length; ++i)
      if ( arguments[i] < result )
         result = arguments[i];
   return result;
}

min();               // retorna undefined
min(3);              // retorna 3
min(3, 0.07, -0.5);  // retorna -0.5
Una función que recibe un número arbitrario de argumentos. Correr este ejemplo.

Ejercicio 6. Escriba una función que calcule el promedio de una cantidad arbitraria de parámetros. Si es invocada sin parámetros retorna undefined. Llame su función con algunos parámetros y compruebe que el resultado es correcto.

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, las cuales 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.

// Crea una "variable estática" como propiedad de la función. JavaScript "parsea" el código
// antes de ejecutarlo, por lo que el nombre de la función puede usarse antes de ser declarada
countCalls.count = 0;

function countCalls()
{
   // Usar la propiedad como si fuese una variable estática
   document.write('<p>countCalls() ha sido llamada ' + ++countCalls.count + ' veces</p>');
}

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:

// Construye un rectángulo. Recibe un objeto en el parámetro oculto this
function Rectangle(w, h)
{
   // Declara propiedades y las inicializa
   this.width = w;
   this.height = h;

   // También puede crear métodos
   this.area = function() { return this.width * this.height; }
}

Las funciones constructoras son 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. Finalmente retorna la dirección de memoria del nuevo objeto.

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 el programador naturalmente espera. 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.

Como la funciones constructoras son funciones normales, también tienen una propiedad prototype, y cada vez que se invoca el operador new, éste implícitamente agrega una propiedad al nuevo objeto para que apunte al prototype de la función constructora. 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

El lenguaje JavaScript especifica que 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:

// El programador define una pseudoclase con una función constructora
function Circle(radius) { this.radius = radius; }

// Y un método que será compartido por todos los objetos creados con new Rectangle()
Circle.prototype.area = function() { return Math.PI * this.radius * this.radius; }

// circle1 y circle2 tienen su respectivo radius, son copias independientes
var circle1 = new Circle(7);
var circle2 = new Circle(2.5);

// circle1 y circle2 comparten el mismo método area()
if ( circle1.area === circle2.area )
{
   document.write('circle1.area() = ', circle1.area() ); // Imprime 153.94
   document.write('circle2.area() = ', circle2.area() ); // Imprime 19.63
}
else
   document.write('Circles do not share the same area() method!');

// El programador trata de modificar el método area() a traves de un objeto
circle2.area = function() { return Math.PI * this.radius * this.radius / 2; }

// Lo anterior crea una propiedad area() sólo en circle2 que tapa la del prototipo
document.write('circle1.area() = ', circle1.area() ); // Imprime 153.94
document.write('circle2.area() = ', circle2.area() ); // Imprime 9.82

// Al modificar una propiedad del prototipo afecta a todos los objetos, creados o nuevos
Circle.prototype.area = function() { return 0; }

// Un nuevo objeto aparece
var circle3 = new Circle(3.1);

// Todos los objetos reflejan el cambio excepto aquellos que han sobrescrito la propiedad
document.write('circle1.area() = ', circle1.area() ); // Imprime 0
document.write('circle2.area() = ', circle2.area() ); // Imprime 9.82
document.write('circle3.area() = ', circle3.area() ); // Imprime 0
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().

Ejercicio 7. Escriba una jerarquía de pseudoclases (funciones constructoras) que generen un documento XHTML 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 cnstruidos 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 XHTML 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:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Random document</title>
   <script type="text/javascript" src="random_doc.js"></script>
   <script type="text/javascript">
      var doc = new Document();
      doc.printHeader();
   </script>
</head>

<body>
   <script type="text/javascript">
      doc.print();
   </script>
</body>
</html>

Ejercicio 8. 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.

Evaluación

Comprima los archivos XHTML, CSS y JavaScript que haya creado durante el laboratorio. Súbalos a la plataforma educativa (Moodle) de la ECCI, en la asignación con título Tarea09.