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
Este laboratorio continúa el aprendizaje autodidáctico del lenguage JavaScript. Algunos ejercicios requieren el trabajo hecho por el estudiante en el Laboratorio 6.
Sección presente en el laboratorio 6.
Sección presente en el laboratorio 6.
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
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>');
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.
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.
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.
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é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] |
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); }
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
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"
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
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
.
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
.