Programación: Javascript Avanzado
De Daniel Pecos
Contenido |
Introducción
Javascript es el tipo de lenguaje que se suele aprender sobre la marcha, sin un estudio a fondo de sus capacidades y características. Es por ello por lo que muchas veces se escribe un código Javascript enrevesado y de poca calidad.
Hay muchas páginas introductorias a este lenguaje, pero no hay tantas que traten las características más avanzadas de este lenguaje. Además, y a pesar de la opinión de mucha gente, este lenguajes es el más utilizado para Web 2.0, por lo que si estás interesado en el desarrollo web, te recomiendo que continues leyendo.
A continuación he recopilado una serie de temas avanzados que no se suelen encontrar en las páginas introductorias o bien los cuales no se suelen tratar de forma explícita.
Espero que os guste.
Conceptos avanzados sobre el lenguaje
Operadores, tipos y funciones especiales
- undefined: es el valor primitivo de una variable que no ha sido inicializada.
- Infinity: es el valor primitivo que representa un valor numérico positivo infinito. Existe la función isFinite() que, aplicada sobre un valor, indica si dicho número es finito o no.
- NaN: significa Not-A-Number y es el valor obtenido al convertir a número un valor que no lo es (p.ej. una cadena de texto que no representa un número, undefined, etc). Existe la funcion isNan() que permite comprobar si un valor es NaN.
- === y !==: son iguales a los operadores de igualdad y desigualdad normales, con la diferencia de que no hacen conversión de tipos automática. Esto implica que estos operadores además de tener en cuenta el valor al hacer la comparación, también tendrán en cuenta el tipo del valor. Así pues:
alert(1 == "1") // mostrará true alert(1 === "1") // mostrará false.
Lista de parámetros de una función
function foo() {
var argv = foo.arguments;
var argc = argv.length;
for (var i = 0; i < argc; i++) {
alert("Argumento " + i + " = " + argv[i]);
}
}
JSON
AJAX
Logging
Función que permite hacer logging en Firefox y Safari:
function log(msg) {
// IE no soporta logging
if (window.console) // safari
window.console.log(msg);
else if (!document.frames) // firefox
dump(msg+"\n");
}
Programación Orientada a Objetos (POO - OOP)
Pues sí, existe la POO en Javascript, aunque no es tan formal como en otros lenguajes y no se suela utilizar comúnmente. Como buen lenguaje de scripting, permite hacer una declaración de tipos de forma dinámica, es decir, permite crear o modificar tipos o clases sobre la marcha. En esta sección intentaré hacer una breve introducción a cómo se utiliza POO en Javascript, así como una pequeña descripción de las técnicas a utilizar.
Pero antes de comenzar, cabe hacer una breve reflexión: ¿por qué utilizar POO en mi código javascript? Básicamente por reutilización de código y por naturalidad:
- Reutilización de código porque podemos definir diferentes estructuras de datos y sus métodos asociados una única vez, y tener varios objetos que sigan esta definición sin necesidad de reescribir la estructura de nuevo.
- Naturalidad porque con la POO las estructuras de datos llevan asociada la lógica necesaria para su utilización, manteniendo la coherencia entre datos y funcionalidad. Esto permite una mayor sencillez y claridad de su utilización y, por tanto, del código.
Primeros pasos: creación de un objeto
Existen dos formas de creación dinámica de objetos en javascript: usando el operador new o con notación literal. A continuación mostraré un pequeño ejemplo de cómo crear un objeto utilizando el operador new:
var persona = new Object();
¿Sencillo, verdad? Vamos a entrar un poco más en detalle. Javascript, como ya se ha dicho, es un lenguaje de scripting, lo cual implica, entre otras, que se pueden añadir propiedades y métodos de forma dinámica (en tiempo de ejecución) a los objetos. Así pues, para crear un objeto complejo persona, lo haríamos de la siguiente forma:
var persona = new Object();
persona.nombre = "Daniel";
persona.apellido1 = "Pecos";
persona.apellido2 = "Martínez";
persona.nombreCompleto = function () {
return this.nombre + " " + this.apellido1 + " " + this.apellido2;
};
Podemos observar que en ningún momento se ha declarado explícitamente que el objeto referenciado por la variable persona tendrá las propiedades nombre, apellido1 y apellido2 o el método nombreCompleto, sino que se han declarado e instanciado sobre la marcha, en caliente. Esto es una de las mayores facilidades (y la mayor fuente de problemas de difícil detección) que ofrece un lenguaje de scripting.
Existe una segunda forma de conseguir un objeto igual al anterior, pero escribiendo menor cantidad de código, llamada notación literal:
var persona = {
nombre : "Daniel",
apellido1 : "Pecos",
apellido2 : "Martínez",
nombreCompleto : function () {
return this.nombre + " " + this.apellido1 + " " + this.apellido2;
}
};
Como se puede ver, esta forma resulta más concisa que la anterior. Además esta sintaxis resulta igual de potente que la alternativa new:
var rectangulo = {
vertice_sup_izq : { x : 1, y : 1 },
vertice_sup_der : { x : 3, y : 5 },
colores_aristas : { "FF0000", "00FF00", "0000FF", "CAFE00" }
};
Definición e incialización de objetos
Ya hemos visto cómo crear de forma dinámica objetos en Javascript, pero también disponemos de la opción clásica de los lenguajes de programación compilados: la definición de tipos o clases. En Javascript no existe el concepto de clase propiamente dicho, aunque, siendo poco riguros, utilizaré los conceptos clase y definición de tipos indistintamente. Veamos primero un ejemplo:
function Persona(nom, ap1, ap2) {
this.nombre = nom;
this.apellido1 = ap1;
this.apellido2 = ap2;
this.nombreCompleto = function () {
return this.nombre + " " + this.apellido1 + " " + this.apellido2;
};
}
¿Qué diferencias vemos con respecto al código del apartado anterior?
- No se está instanciando ningún objeto.
- Se está utilizando una función para la definición del tipo.
- Se está utilizando this para referenciar a la nueva instancia.
Así pues, con esta definición de tipo, podríamos escribir el siguiente código:
var mitnick = new Persona ("Kevin", "Mitnick", null);
var wozniak = new Persona ("Stephen", "Wozniak", null);
var yo = new Persona ("Daniel", "Pecos", "Martínez");
alert(mitnick.nombreCompleto() + " y " + wozniak.nombreCompleto() + " son grandes hackers");
La gran ventaja de esta opción es que podemos reutilizar la definición de la clase para instanciar diferentes objetos del mismo tipo, mientras que de la forma dinámica, estamos instanciando y definiendo los objetos al mismo tiempo, por lo que si queremos crear diferentes instancias del mismo tipo, nos vemos obligados a redefinir el tipo de cada objeto, con el peligro de cometer algún fallo y que las estructuras difieran.
Así pues, la definición de tipos será útil cuando precisemos crear varios objetos de una misma clase. Además, y aunque no lo he comentado, cuando se utiliza la definición de tipos, automáticamente, cada vez que se crea un objeto de ese tipo, se está realizando su inicialización, utilizando los posibles parámetros que se pasen al constructor en la sentencia new Clase (en caso de que se pase un número inferior de parámetros a los que se han definido, los que ocupen las n últimas posiciones no estarán definidos, como en cualquier función Javascript).
Todavía es posible rizar más aún el rizo, ya que podemos añadir de forma dinámica nuevos métodos y atributos a una definición de tipo ya existente. Por ejemplo, si a la clase Persona queremos añadirle un nuevo método de forma dinámica, lo haremos utilizando el atributo especial prototype sobre la propia clase. Veamos un ejemplo:
Persona.prototype.historia = function (texto) { this.historiaPersonal = texto };
mitnick.historia("Kevin Mitnick fue encarcelado acusado de ...");
wozniak.historia("Wozniak y Jobs crearon la compañía Apple en ...");
yo.historia("Fundé estilohacker.com en 2008...");
Para este ejemplo hemos utilizado la clase Persona que habíamos definido previamente, aunque no habría habido ningún problema en añadir nueva funcionalidad a alguna de las clases predefinidas en Javascript como String, Array o Date:
String.prototype.reverse = function () {
splitext = this.split("");
revertext = splitext.reverse();
reversed = revertext.join("");
return reversed;
}
Herencia
Ya hemos visto las distintas formas de definir e instanciar un objeto en Javascript. El siguiente paso que daremos será el uso de la herencia.
Al igual que no es correcto hablar de clases como tales, tampoco lo podemos hacer de la herencia, aunque al igual que en el caso anterior, podemos definir algo que funciona de forma muy semejante. Comencemos viendo un ejemplo:
// tomando la definición de Persona del apartado anterior:
function Hacker(nom, ap1, ap2, proy) {
this.superclase = Persona;
this.superclase(nom, ap1, ap2);
this.proyecto = proy;
this.nombreCompletoYProyecto = function () { return this.nombreCompleto() + ": " + this.proyecto; };
}
var mitnick = new Hacker ("Kevin", "Mitnick", null, "Phreacking");
alert(mitnick.nombreCompleto());
alert(mitnick.nombreCompletoYProyecto());
Podemos ver que lo único que difiere de una definición de tipo usual son las dos primeras líneas del constructor en las que invocamos la definición de tipo Persona, pero sobre el objeto que actualmente se está instanciando (presta atención en que se utiliza this). Es decir, la herencia en Javascript puede ser vista como un agregado de definiciones, debiendo especificar esta agregación de forma explícita.
En nuestro ejemplo, si obviáramos las dos primeras líneas del constructor del tipo Hacker, tendríamos una definición de tipo semejante a la del caso anterior. Al ejecutarse estas líneas, lo que se hace es añadir los atributos y métodos que se han definido en el tipo Persona sobre el objeto Hacker que se está instanciando, obteniendo un objeto que posee los atributos de Persona y los de Hacker.
Todos los atributos y métodos definido en el tipo padre tienen el mismo nivel de acceso que los definidos en la clase hija, de hecho en Javascript no hay más que un nivel de acceso que podríamos denominar como público. A ésta regla existe una excepción: los atributos y métodos definidos a través de prototype para la clase padre, no son heredados por la clase hija. Ésto es así porque realmente el objeto hijo es del tipo hijo, con la característica de que se le han añadido los atributos y métodos que se habían definido para la clase padre. Veamos un ejemplo aclaratorio:
function Persona(nom, ap1, ap2) {
this.nombre = nom;
this.apellido1 = ap1;
this.apellido2 = ap2;
this.nombreCompleto = function () {
return this.nombre + " " + this.apellido1 + " " + this.apellido2;
};
}
Persona.prototype.historia = function (texto) { this.historiaPersonal = texto };
function Hacker(nom, ap1, ap2, proy) {
this.superclase = Persona;
this.superclase(nom, ap1, ap2);
this.proyecto = proy;
this.nombreCompletoYProyecto = function() { return this.nombreCompleto() + ": " + this.proyecto; };
}
// instanciamos un objeto del tipo hijo: podemos acceder a los métodos definidos en la clase padre
var mitnick = new Hacker("Kevin", "Mitnick", null, "Phreacking");
alert(mitnick.nombreCompleto());
alert(mitnick.nombreCompletoYProyecto());
// instanciamos un objeto del tipo padre: podemos acceder a los métodos definidos a través de prototype
var yo = new Persona ("Daniel", "Pecos", "Martínez");
yo.historia("Fundé estilohacker.com en 2008...");
alert(yo.historiaPersonal);
// error: historia() pertenece al tipo Persona (definido mediante prototype), no a la clase Hacker
mitnick.historia("Kevin Mitnick fue encarcelado acusado de ...");
alert(mitnick.historiaPersonal);
Manipulación del DOM
Inserción dinámica de elementos
Componentes HTML
Ejemplo de creación dinámica de un enlace:
var enlace = document.createElement("a");
enlace.setAttribute("href", "http://estilohacker.com");
enlace.appendChild(document.createTextNode("EstiloHacker"));
document.appendChild(enlace);
CSS
Enlaces a ficheros CSS:
try {
var headNode = document.getElementsByTagName("head")[0];
var newCSS = document.createElement('link');
newCSS.rel = 'stylesheet';
newCSS.href = url;
headNode.appendChild(newCSS);
} catch (e) {
alert("Error insertando hoja de estilos");
}
Script
Enlaces a ficheros JS:
try {
var headNode = document.getElementsByTagName("head")[0];
var newScript = document.createElement('Script');
newScript.type = 'text/javascript';
newScript.src = url;
headNode.appendChild(newScript);
} catch (e) {
alert("Error insertando script (enlace)");
}
Scripts embebidos en la web:
try {
var headNode = document.getElementsByTagName("head")[0];
var newScript = document.createElement('Script');
newScript.type = 'text/javascript';
newScript.text = contenido;
headNode.appendChild(newScript);
} catch (e) {
alert("Error insertando script");
}

