Guía Rápida para JavaScript Casi Avanzado

excursión a algunas características OO

por
traducción
última actualización: Febrero 21, 2006

Hey, yo no sabía que podías hacer eso

Si tú eres un desarrollador we y vienes del mismo lugar que yo, probalemente has usado un poco de Javascript en tus páginas we, la mayoría para pegar la interface de usuario (UI).

Hasta hace poco, conocía que Javascript tenía más capacidades OO que las que empleaba, pero no me sentía a gusto para usarlas. Cuando los navegadores comenzaron a soportar más características normalizadas de DOM y Javascript, me pareció viable escribir código más complejo y funcional para correr en el cliente. Esto ayudado por el nacimiento del fenómeno AJAX.

Cuando comenzamos a aprender lo que toma escribir nuestras buenas aplicaciones con AJAX, empezamos a notar que el Javascript que solemos usar es realmente tan sólo la punta del iceberg. Vemos ahora que Javascript se usa más allá de los quehaceres simples de UI como la comprobación de la entrada y otras tareas frívolas. El código del cliente ahora es mucho más avanzado y compuesto, tanto como una aplicación real de escritorio o un cliente pesado de un ambiente cliente-servidor. Vemos las librerías de clases, objetos modelo, jerarquías, patrones, y muchas otras cosas que acostumbramos sólo ver del lado del código del servidor.

En muchas formas podemos decir que súbitamente pusieron la barra más arriba que antes. Lleva mucha destreza escribir aplicaciones para la nueva Web y necesitamos mejorar nuestra habilidad con Javascript para lograrlo. Si intentas usar muchas de las librerías existentes de javascript, como Prototype.js, Scriptaculous, moo.fx, Behavior, YUI, etc, eventualmente te encontrarás leyendo el código JS. Quizá porque quieras aprender cómo lo hacen, o porque eres curioso, o más a menudo porque es la única forma de figurarse cómo usarla, ya que la documentación no parece ser altamente contemplada en la mayoría de las librerías. Cualquiera sea el caso, encararás alguna técnica de kung-fú que será extraña y asustadiza si no has visto algo como eso antes.

El propósito de este artículo es precisamente explicar los tipos de construcciones que a muchos de nosotros no nos es familiar aún.

Artículo relacionado

Documentación de Prototype.js

JSON

La Notación de Objetos Javascript (JSON), es una de las palabras de moda que vuela alrededor del tema AJAX. JSON, pongamos, es una forma de declarar un objeto en javascript. Veamos un ejemplo ya y notemos cómo es de simple.

var myPet = { color: 'negro', leg_count: 4, communicate: function(repeatCount){ 
for(i=0;i<repeatCount;i++) alert('Guau!');} };
			

Agreguemos un poco de formateo así luce más como usualmente lo encontraremos por allí:

var myPet = {
	color: 'negro', 
	legCount: 4, 
	communicate: function(repeatCount){
		for(i=0;i<repeatCount;i++)
			alert('Guau!');
	} 
};
			

Aquí creamos una referencia a un objeto con dos propiedades (color y legCount) y un método (communicate). No es difícil darse cuenta de que las propiedades y métodos del objeto son definidos como una lista delimitada por comas. Cada uno de los miembros es introducido por nombre, seguido de : y su definición. En el caso de las propiedades es fácil, va el valor de la propiedad. Los métodos son creados asignándoles una función anónima, la cual explicaremos mejor más abajo de estas líneas. Después de que el objeto es creado y asignado a la variable myPet, podemos usarlo como aquí:

alert('mi mascota es ' + myPet.color);
alert('mi mascota tiene ' + myPet.legCount + ' patas');
//si tú eres un perro, ladra 3 veces:
myPet.communicate(3);
			

Tú verás que JSON es muy usado por todas partes en el JS de estos días, como argumentos a funciones, retorno de valores, respuesta de servidores (en strings), etc.

Qué quieres decir? Una función también es un objeto?

Podría ser inusual a los desarrolladores que nunca pensaron en esto, pero en JS una función es también un objeto. Puedes pasar una función como un argumento a otra función de la misma forma como pasas un string, por ejemplo. Esto es ampliamente usado y muy cómodo.

Mira este ejemplo. Pasaremos funciones a otra función que las usará.

var myDog = {
	bark: function(){
		alert('Guau!!');
	}
};

var myCat = {
	meow: function(){
		alert('Soy un gato perezoso. No voy a maullar para ti.');
	}
};
 
function annoyThePet(petFunction){
	//veamos lo que la mascota puede hacer
	petFunction();
}

//molestar al perro:
annoyThePet(myDog.bark);
//molestar al gato:
annoyThePet(myCat.meow);
			

Nota que pasaremos myDog.bark y myCat.meow sin los paréntesis "()". Si los hubiéramos agregado no podríamos pasarlos a la función, en vez de eso estaríamos llamando al método y pasando el valor de retorno, undefined en ambos casos.

Si quieres hacer que mi gato perezoso empiece a ladrar, puedes fácilmente hacer esto:

myCat.meow = myDog.bark;
myCat.meow(); //dice 'Guau!'
			

Arreglos, items, y miembros de objetos

Las siguientes dos líneas hacen lo mismo.

var a = new Array();
var b = [];
			

Como estoy seguro que ya sabes, puedes acceder a items individuales de un arreglo usando los corchetes:

var a = ['primero', 'segundo', 'tercero'];
var v1 = a[0];
var v2 = a[1];
var v3 = a[2];
			

Pero no estás limitado a índices numéricos. Puedes acceder a cualquier miembro de un objeto JS usando su nombre, en un string. El siguiente ejemplo crea un objeto vacío, y agrega algunos miembros por nombre.

var obj = {}; //new, objeto vacío
obj['member_1'] = 'éste es el valor del miembro';
obj['flag_2'] = false;
obj['some_function'] = function(){ /* hacer algo */};
			

El código de arriba causa el mismo efecto que el siguiente:

var obj = {
	member_1:'éste es el valor del miembro',
	flag_2: false,
	some_function: function(){ /* hacer algo */}
};
			

En muchas formas, la idea de objetos y arreglos asociativos (hashes) en JS no son distinguibles. Las siguientes dos líneas hacen la misma cosa también.

obj.some_function();
obj['some_function']();
			

Suficiente sobre objetos, puedo tener una clase ahora?

El gran poder de los lenguajes de programación orientados a objetos deriva del uso de clases. No creo que pudiera haber adivinado cómo definir clases en JS usando únicamente mi experiencia previa con otros lenguajes. Juzga por ti mismo.

//definiendo una nueva clase llamada Pet
var Pet = function(petName, age){
	this.name = petName;
	this.age = age;
};

//creamos un objeto de la clase Pet
var famousDog = new Pet('Santa\'s Little Helper', 15);
alert('Esta mascota se llama ' + famousDog.name);
			

Veamos cómo agregar un método a nuestra clase Pet. Estaremos usando la propiedad prototype que todas las clases tienen. La propiedad prototype es un objeto que contiene todos los miembros que cualquier objeto de la clase puede tener. Incluso las clases que vienen con JS, como String, Number, y Date tienen un objeto prototype al que podemos agregar métodos y propiedades para hacer que cualquier objeto de esa clase automáticamente gane este nuevo miembro.

Pet.prototype.communicate = function(){ 
	alert('No sé qué debería decir, pero mi nombre es ' + this.name);
};
			

Aquí es cuando una librería como prototype.js se vuelve cómoda. Si estamos usando prototype.js, podemos hacer que nuestro código luzca más claro (al menos en mi opinión).

var Pet = Class.create();
Pet.prototype = {
	//nuestro 'constructor'
	initialize: function(petName, age){
		this.name = petName;
		this.age = age;
	},
	
	communicate: function(){
		alert('No sé qué debería decir, pero mi nombre es ' + this.name);
	}
};	
			

Funciones como argumentos, un patrón interesante

Si nunca ha trabajado con lenguajes que soporten cerramientos (closures), como Ruby o C#2.0, podrás encontrar el siguiente idiomismo algo desconcertante.

var myArray = ['primero', 'segundo', 'tercero'];
myArray.each( function(item, index){
	alert('El item en la posición #' + index + ' es:' + item);
});
			

Waw! Déjame explicar qué es lo que hago aquí antes de que decidas que me fui muy lejos y navegues hacia un mejor artículo que éste.

Primero de todo, en el ejemplo de arriba estamos usando la librería prototype.js, la cual agrega la función each a la clase Array. La función each acepta un argumento que es un objeto función. Esta función, a su vez, será llamada una vez para cada item en el arreglo, pasando dos argumentos cuando sea llamada, el item y el índice para el actual item. Llamemos a esta función nuestra función iteradora. Podríamos también haber escrito el código como sigue:

function myIterator(item, index){
	alert('El item en la posición #' + index + ' es:' + item);
}

var myArray = ['primero', 'segundo', 'tercero'];
myArray.each( myIterator );
			

Pero entonces no se estaría haciendo como todos los chicos buenos de la escuela, correcto? Más seriamente, aún, este último formato es más simple de entender pero nos obliga a saltar en el código en busca de la función myIterator. Es mejor tener la lógica de la función iteradora justo allí mismo donde es llamada. Además, en este caso, no necesitaremos la función iteradora en nungún otro lugar de nuestro código, así que podemos transformarla en una función anónima sin problemas.

Mira nuevamente el ejemplo original, un poco resaltado para hacer las cosas más claras, espero.

				
var myArray = ['primero', 'segundo', 'tercero'];
myArray.each( function(item, index){
	alert('El item en la posición #' + index + ' es:' + item);
} );
			

This es ésto pero a veces ésto es aquello

Uno de los más comunes problemas que tenemos con JS cuando comenzamos a escribir nuestro código es el uso de la palabra clave this. Podría ser un real tripware.

Como mencionamos antes, una función es también un objeto en JS, y a veces no notamos que estamos pasando una función.

Toma este estracto de código como un ejemplo.

function buttonClicked(){
	alert('botón ' + this.id + ' fue cliqueado');
}

var myButton = document.getElementById('someButtonID');
var myButton2 = document.getElementById('someOtherButtonID');
myButton.onclick = buttonClicked;
myButton2.onclick = buttonClicked;
			

Debido a que la función buttonClicked es definida fuera de cualquier objeto podríamos tender a pensar que la palabra clave this contendrá una referencia al objeto window o document (asumiendo que este código está en medio de una página HTML vista en un navegador).

Pero cuando corremos el código vemos que trabaja como se deseaba y muestra el id del botón cliqueado. Lo que pasó es que hicimos que el método onclick de cada botón contenga la referencia al objeto buttonClicked, reemplazando lo que sea hubo allí antes. Ahora cuando el botón es cliqueado, el navegador ejecutará algo similar a la siguiente línea .

myButton.onclick();
			

Esto no es tan confuso después de todo, cierto? Pero mira qué sucede cuando empiezas a tener otros objetos con los que tratar y quieres actuar sore sus eventos como el click del botón.

var myHelper = {
	
	formFields: [ ],
	
	emptyAllFields: function(){
		for(i=0; i<this.formFields.length; i++){
			var elementID = this.formFields[i];
			var field = document.getElementById(elementID);
			field.value = '';
		}
	}
};

//dice con cuáles campos del formulario queremos trabajar
myHelper.formFields.push('txtName');
myHelper.formFields.push('txtEmail');
myHelper.formFields.push('txtAddress');

//limpiar las cajas de texto
myHelper.emptyAllFields();

var clearButton = document.getElementById('btnClear');
clearButton.onclick = myHelper.emptyAllFields;
			

Así que piensas, bueno, ahora puedo cliquear el botón Clear en mi página y esas tres cajas de texto se vaciarán. Luego intentas cliquear el botón y sólo obtienes un error en tiempo de corrida. El error será relativo a (adivinas a qué?) la palabra clave this. El problema es que this.formFields no está definido si this contiene una referencia al botón, lo cual es precisamente lo que está sucediendo. Una rápida solución podría ser reescribir nuestra última línea de código.

clearButton.onclick = function(){ myHelper.emptyAllFields(); };
			

De esta manera creamos una nueva función que llama a nuestro método helper desde dentro del contexto del objeto helper.


Este artículo no está terminado aún. Si encuentras ejemplos que no andan, información incorrecta, links rotos, etc, por favor e intentaré corregirlos tan pronto como sea posible.