Objetos y Clases en JavaScript

¡Saludos visitante! si deseas comentar o hacer una pregunta sobre este post por favor dirígete a la nueva dirección en http://variabletecnica.com.ve. La página que estás leyendo dejará de estar disponible el 15/11/2015. Gracias, y disculpa las molestias 🙂 Cuando comencé a programar en ASP.Net fue un poco difícil para mí aprender JavaScript, en parte debido…


¡Saludos visitante! si deseas comentar o hacer una pregunta sobre este post por favor dirígete a la nueva dirección en http://variabletecnica.com.ve. La página que estás leyendo dejará de estar disponible el 15/11/2015. Gracias, y disculpa las molestias 🙂

Cuando comencé a programar en ASP.Net fue un poco difícil para mí aprender JavaScript, en parte debido a que inicialmente hacía casi todo del lado del servidor (debo confesar mi adicción al UpdatePanel -_-‘), pero principalmente fue porque el lenguaje JavaScript es muy permisivo, y para hacer las cosas bien debes ser consistente y prestar atención.

Una definición popular de JavaScript nos dice que es “un lenguaje de programación interpretado, basado en ECMAScript. Orientado a objetos, basado en prototipos, imperativo, débilmente tipado, y dinámico”.

¿Y qué significa todo esto? Bueno, si ya lo sabes salta a la siguiente sección y empecemos, sino sigue leyendo. Lenguaje de Programación Interpretado: el interprete (un navegador o un servidor Web) lee el código fuente sin compilar y lo ejecuta a medida que lo lee (aunque podría leerlo primero y precompilarlo “en vivo” para verificar errores de sintaxis o acelerar la ejecución). Orientado a Objetos: con la única excepción de los tipos de dato simple (cadenas, números…) todo en JavaScript se comporta como un objeto, incluso las funciones. Basado en Prototipos: Aunque esté orientado a objetos no existe explícitamente el concepto Clases, pero sí el de Prototipos que junto a las funciones constructoras nos permiten crear objetos del mismo tipo con miembros compartidos (más adelante hablaré sobre esto). Imperativo: cada instrucción indica al interprete, con cierto nivel de abstracción, exactamente qué hacer (por el contrario, lenguajes como SQL son Declarativos).

Las dos últimas características son un poco incomodas para los que venimos de lenguajes como VB, C o Java; Débilmente Tipado: al declarar una variable no se indica el tipo, por lo que una variable que inicialmente almacenaba una cadena, puede luego almacenar un número o una fecha. Dinámico: los objetos en JavaScript pueden modificarse en tiempo de ejecución, por ejemplo, podemos primero declarar un objeto nuevo y unas líneas más adelante agregarle propiedades o funciones nuevas o modificar las existentes.

Crear un objeto en JavaScript

La forma más simple de crear un objeto en Javascript es mediante un Literal de Objeto:

var contacto = {};

Esto creará un nuevo objeto “en blanco”. Pero claro, si queremos crear un objeto se espera que tenga propiedades y funciones para poder hacer algo con él. Hay dos formas igualmente válidas de hacerlo, la primera usando un Literal de Objeto:

var pedroPerez = {
	nombre:'Pedro Pérez',
	nacimiento: new Date(1982, 7, 22),
	casado: false,
	hijos: 0,
	calcularEdad: function(){
		return Math.floor((new Date() - this.nacimiento)/1000/3600/24/365);
	}
};

La segunda forma consiste en aprovechar el Dinamismo de JavaScript, creando un objeto “vacío” al que le agregamos las propiedades y funciones que necesitemos:

var pedroPerez = {}
pedroPerez.nombre = 'Pedro Pérez';
pedroPerez.nacimiento = new Date(1982, 7, 22);
pedroPerez.casado = false;
pedroPerez.hijos = 0;
pedroPerez.calcularEdad = function(){
					return Math.floor((new Date() - this.nacimiento)/1000/3600/24/365);
				}

En este caso, cuando asignamos un valor a una propiedad de objeto que no existe, ésta se crea inmediatamente para almacenar el nuevo valor.

La primera forma, usando un Literal de Objeto, es preferible en lugar de la segunda. La razón es que algunos motores JavaScript (como V8 en Google Chrome o SpiderMokey en Firefox) intentan optimizar la ejecución del código (ver referencias al final), y una de las formas de hacerlo consiste en detectar aquellos objetos que cambien poco en el código para almacenarlos en memoria de una forma más eficiente. Cuando el motor JavaScript detecta que el objeto es modificado con cierta frecuencia (agregándole nuevas propiedades o borrándolas con Delete), es más difícil de optimizar haciendo el código menos eficiente al momento de ejecutarlo.

Clases en JavaScript

Siendo un lenguaje Basado en Prototipos JavaScript no cuenta con el concepto de Clases, sino que los objetos de un mismo “tipo” son creados a partir de un modelo o prototipo.

En la sección anterior creamos dos objetos utilizando un Literal de Objeto como prototipo. Una forma equivalente de crear un objeto es mediante una función constructora que define al prototipo:

var contacto = new Object();

En este ejemplo, el operador new indica que se debe asignar a la variable contacto una nueva instancia creada a partir de la función Object. Entonces, esta función cumple con el papel de Función Constructora.

Ahora creemos una función constructora personalizada, que genere instancias de tipo Contacto:

var Contacto = function(nombre){
	this.nombre = nombre || '';
	this.nacimiento = new Date();
	this.casado = false;
	this.hijos = 0;
	this.calcularEdad = function(){
		return Math.floor((new Date() - this.nacimiento)/1000/3600/24/365);
	};
}

var juanPerez = new Contacto('Juan Pérez');
juanPerez.nacimiento = new Date(1982, 7, 22);
console.log(juanPerez.nombre + ': ' + juanPerez.calcularEdad().toString())

var margarita = new Contacto('Margarita Ortiz');
margarita.nacimiento = new Date(1979, 4, 17);
console.log(margarita.nombre + ': ' + margarita.calcularEdad().toString())

Como podemos ver, la función Contacto se comporta de modo similar a como lo hace el constructor de una clase.

Hay un par de detalles que aclarar acerca de esta función: primero que nada la función no tiene return, ésto es debido a que al usar la palabra clave new la función siempre creará y devolverá un nuevo objeto al cual se le han asignado las variables y funciones mediante la palabra clave this (como si al final de la función se hubiese colocado return this). Aun así, es posible crear manualmente un nuevo objeto dentro del constructor y devolverlo con return, pero este tema lo tocaré en otro artículo.

El segundo detalle al que me refería es que al crear las dos variables, juanPerez y margarita, la función constructora se ejecutó dos veces, permitiendo que las propiedades de ambas instancias sean independientes (al cambiar alguna propiedad de juanPerez no se modifica esa propiedad en margarita), pero como consecuencia, cada instancia también tiene una copia de la función calcularEdad. Puedes probarlo ejecutando este código al final del anterior (o este jsFiddle):

//Probamos que son dos funciones diferentes (diferentes instancias)
console.log('Misma función: ' + (juanPerez.calcularEdad===margarita.calcularEdad));
//Imprime 'Misma función: false' porque las dos propiedades 
//son referencias a distintos objetos (fijate que no estamos comparando
//los resultados de la función, sino las funciones mismas).

Esto último no es problema si solo piensas crear una o unas pocas instancias de la nueva “clase”, pero si planeas crear decenas o miles debes considerar el consumo de recursos que eso implica. Pero tranquilo, que para eso hay una solución.

Prototipos de Objetos

Vamos a cambiar un poco la función Contacto, para evitar crear múltiples instancias de la función calcularEdad:

var Contacto = function(nombre){
	this.nombre = nombre || '';
	this.nacimiento = new Date();
	this.casado = false;
	this.hijos = 0;
}

Contacto.prototype.calcularEdad = function(){
	return Math.floor((new Date() - this.nacimiento)/1000/3600/24/365);
};

var juanPerez = new Contacto('Juan Pérez');
juanPerez.nacimiento = new Date(1982, 7, 22);
console.log(juanPerez.nombre + ': ' + juanPerez.calcularEdad().toString())

var margarita = new Contacto('Margarita Ortiz');
margarita.nacimiento = new Date(1979, 4, 17);
console.log(margarita.nombre + ': ' + margarita.calcularEdad().toString())

//Probamos que son la misma función (misma instancia)
console.log('Misma función: ' + (juanPerez.calcularEdad===margarita.calcularEdad));
//Imprime 'Misma función: true' porque las dos propiedades 
//son referencias a la misma función (de nuevo, estamos comparando
//las funciones mismas, no los resultados de ejecutarlas).

Al ejecutar el código anterior (o este jsFiddle) vemos que ahora ambas instancias, juanPerez y Margarita, comparten la misma función calcularEdad, pero el resto de las propiedades son independientes.

Veamos como funciona: Todos los objetos (y las funciones, ya que también son objetos) tienen una propiedad prototype, que nos permite acceder e incluso modificar al prototipo de cualquier objeto. Si agregamos o modificamos una función o propiedad en el prototipo, éste cambio se efectuará también en cualquier instancia que se base en el mismo prototipo.

Para cerrar este artículo mencionaré una de tantas ventajas que tiene el uso de prototipos. Ya sea que estés comenzando a programar en JavaScript o si tienes tiempo haciéndolo, debes saber que no todos los motores de Javascript (navegadores y servidores) lo implementan de la misma forma, incluso entre diferentes versiones del mismo motor. Por ejemplo, en la mayoría de los navegadores Web, las cadenas (String) cuentan con la función trim, que elimina los espacios en blanco al principio y al final de la misma, sin embargo, Internet Explorer no dio soporte para la función trim hasta la versión 9, así que la función no está disponible para IE8 o anteriores.

Si necesitas dar soporte a versiones anteriores de IE, entonces debes modificar el prototipo de la clase String para agregarle la función trim si es que no está definida. Veamos:

if(!String.prototype.trim){
	String.prototype.trim = function () {
		return this.replace(/^\s+|\s+$/g,'');
	};
}

Lo primero que hace el código anterior es verificar si el prototipo de String tiene definida una función trim, ésto se conoce como Detección de Características (Feature Detection). Si la función no está definida en el prototipo, entonces la crea. De este modo podemos usar la función trim, incluso en viejas versiones de IE u otro navegador que no tenga soporte para esa función de forma nativa.

Referencias

  • Recomendación de Google para la creación de métodos y propiedades (en inglés): http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml?showone=Method_and_property_definitions#Method_and_property_definitions
  • Artículo en Smashing Magazine sobre programación rápida y eficiente en Javascript: http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
  • Programación basada en Prototipos: http://es.wikipedia.org/wiki/Programaci%C3%B3n_basada_en_prototipos
  • Función String.trim (documentación en MDN, en Inglés): https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/Trim

Derecho de uso

Los contenidos generados por el autor de este artículo (explicaciones, código fuente, y archivos adjuntos creados por el autor) están disponibles bajo licencia CC BY-SA 3.0, y pueden ser usados, derivados y compartidos bajo los términos indicados en la misma. Los contenidos no generados por el autor de este artículo son propiedad de sus respectivos dueños y están regidos por las licencias que estos hayan dispuesto.

Cita del Día

Painting from nature is not copying the object; it is realizing one’s sensations.
Paul Sezzane


4 respuestas a “Objetos y Clases en JavaScript”

Soluciones claras y simples



Ing. Industrial, dedicado a la programación en ASP.NET+VB, SQL y Javascript+AJAX; y un poco de Android, Kotling, y Unity 🙂
Valencia, Venezuela



¿QUIERES APOYARME?

¿Te ha sido de ayuda alguno de mis artículos? Generar contenido técnico requiere de tiempo y esfuerzo. Con tu colaboración me puedes ayudar a mantener mi blog activo y actualizado. Si quieres y puedes apoyarme has clic aquí:

https://paypal.me/roimergarcia


ENTRADAS RECIENTES