Objetos en JavaScript

By dedalo534

Introducción

En el artículo de funciones anterior se introdujo el concepto de funciones, enseñando como organizar y reutilizar mejor el código, agrupando funcionalidad individual en funciones lógicas, a las que se pueden llamar cuando se quiera. Ahora, que ya estamos familiarizados con los componentes básicos de la programación en JavaScript, habría que ampliar y mejorar las aplicaciones introduciendo Objetos. Los objetos nos permiten reunir esos conjuntos de funcionalidad que se han definido como funciones, envolviendolos en paquetes con una cierta coherencia, y referirnos a ellos como items simples. Esta habilidad tiene muchos efectos prácticos sobre el código que se puede escribir, incluso si esto suena ligeramente abstracto por el momento.

Puede que no se haya notado, pero, implicitamente, nos hemos visto expuesto a objetos a través de estos artículos. Aquí se dará una comprensión más explícita de como los objetos funcionan en JavaScript, y explicaremos como se puede incrementar la expresividad y la reusabilidad del código.

La estructura de este artículo es la siguiente:

Nota: Existe un ejemplo disponible para descarga y para ejecución, que contiene código que calcula el área de un triángulo, con y sin objetos. Este código está construido con las explicaciones de más abajo. Ejecuta el ejemplo de triángulo con objetos.

Porqué Objetos?

La razón mas simple e importante que hay que tener en cuenta acerca de los objetos es su capacidad para aumentar la traducción a código de los datos y procesos que se están implementando. Como un ejemplo trivial, consideraremos que se ha escrito código que hace un cierto tipo de trabajo con un triángulo. Como todos conocemos, los triángulos en general tienen tres lados, asi que para trabajar con uno de ellos, lo lógico es crear tres variables:

// Esto es un triángulo.
var ladoA = 3;
var ladoB = 4;
var ladoC = 5;

¡Ya tenemos un triángulo!. Bueno, no realmente, ¿no?. Realmente, hemos creado tres variables que necesitamos manejar por separado, y un comentario para acordarnos de lo que estábamos pensando. Tan simple, que no es todo lo claro o usable que debería ser. No hay problema, continuemos y pensemos como podríamos construir determinados cálculos sobre este “triángulo”. Para calcular su área, podríamos escribir una función como la siguiente:

function getArea( a, b, c ) {
  // Devuelve el área de un triángulo usando la fórmula de Herón.
  var semiperimetro =   (a + b + c) / 2;
  var valor         =   semiperimetro * (semiperimetro - a) * (semiperimetro - b) * (semiperimetro - c);
  return Math.sqrt( valor );
}

alert( getArea( sideA, sideB, sideC ) );

Se puede ver que hemos pasado toda la información acerca del triángulo en la función, para poder hacer todos los cálculos. Todos los cálculos relacionados con el triángulo son prácticamente sacados de los datos del triángulo, aunque no tenga mucho sentido este aislamiento.

Además, se han usado unos nombres genéricos para la función y para cada una de las variables: getArea, ladoA, etc. ¿Que pasaría si nos encontramos la próxima semana que necesitamos extender este código para incluir un rectángulo?. Quizás habria que utilizar ladoA y ladoB para los datos del rectángulo, pero estos nombres de variables ya están cogidos. Podríamos utilizar lado1 y lado2, pero podemos apostar a que eso es el origen de confusión y desastre. Probablemente, terminaríamos usando rectanguloLadoA y rectanguloLadoB, y para ser consistente, habríamos vuelto atrás y cambiado el código ya escrito para los triángulos, para utilizar trianguloLadoA y así, lo cual puede llegar a inducir a error. Lo mismo aplica al nombre de la función, me gustaría usar getArea para ambos casos, dado que conceptualmente es el mismo cálculo. Pero no podemos. ¡Tiene que haber un mejor sistema para representar estos datos!.

De la misma forma que tiene sentido crear una función que incluye una serie de tareas, tiene sentido crear un objeto que provea todos esas tareas juntas en una unidad simple. En lugar de estar limitado por los tipos de datos nativos soportados por JavaScript (cadenas, números, booleanos, etc.), los objetos nos permiten construir nuestro propio conjunto de variables de cualquier tipo. Esta flexibilidad nos permite construir estructuras que mapean directamente “las cosas” en las que se está centrado, con la construcción de los programas, y usarlas directamente en nuestro código como tipos primitivos. Aquí, deberíamos construir los objetos triángulo y rectángulo, cada uno conteniendo todos los datos necesarios para conjugar inteligentemente esas formas con las actividades ó cálculos que necesitemos hacer sobre ellos. Con esto en mente, veamos como se plasma esto en código.

Territorio Familiar

Si echamos un vistazo atrás al artículo anterior sobre funciones finales, podremos ver código como:

var obj = document.getElementById( elementID );

y:

obj.style.background = 'rgb('+rojo+','+verde+','+azul')';

¡Sorpresa! ¡Hemos estado utilizando objetos sin siquiera conocerlos!. Exploremos estos trozos de código, para empezar con la sintaxis de objetos de JavaScript.

El código var obj = document.getElementById( elementID ) nos debería sonar bastante familiar. Los paréntesis al final de la sentencia indican que una función de algún tipo está siendo invocada, y que el resultado de la función está siendo almacenado en una variable llamada obj. El único elemento que es nuevo es el punto de la mitad de la sentencia. Como se puede ver, la notación punto es la forma que nos proporciona JavaScript para acceder a los datos de dentro de un objeto. El punto (.) es solamente un operador que está entre dos operandos, como el + y el -.

Por convención, las variables almacenadas en un objeto al que nosotros accedemos via el operador . son, genéricamente hablando, llamadas propiedades. Las propiedades de los objetos que son funciones son llamadas métodos. No hay ninguna magia acerca de estos nombres, los métodos son justamente funciones, las propiedades son variables.

El operador . espera un objeto a su izquierda, y un nombre de propiedad a su derecha; aplicando esto al trozo de código anterior, podemos decir que estamos accediendo al método getElementById del objeto predefinido document (acerca del cuál se podrá leer mucho más en el siguiente artículo sobre recorrer el árbol DOM.

El siguiente trozo de código es un poco mas interesante, tiene dos puntos. Una de las cosas realmente excitantes del soporte de objetos de JavaScript es la posibilidad de encadenar puntos para introducirse en complejas estructuras de objetos. Concretamente, puede encadenar objetos de la misma forma que se puede ejecutar var x = 2 + 3 + 4 + 5; y esperar que resulte 14; las referencias a los objetos se resuelven por si solas, de izquierda a derecha (se puede impresionar a los colegas explicando que esto hace que el operador . de JavaScript sea considerado un “operador asociativo por la izquierda”). En este caso, obj.style.background es evaluado resolviendo un objeto cuya propiedad style es accedida. Se puede hacer esto mas explícito añadiendo parentesis: (obj.style).background.

Creando Objetos

Para construir nuestro propio objeto triángulo, lo crearemos utilizando la siguiente sintaxis:

var triangulo = new Object();

triangulo es ahora una objeto en blanco, esperando que añadamos al vuelo una construcción de tres lados. Esto se puede hacer añadiendo propiedades al objeto utilizando el operador . :

triangulo.ladoA  =   3;
triangulo.ladoB  =   4;
triangulo.ladoC  =   5;

No hay que hacer nada especial para añadir mas propiedades nuevas a un objeto. Cuando JavaScript evalúa el operador ., si estás haciendo un intento de acceder a una propiedad que realmente no existe, la crea por tí. Si tratas de leer una propiedad que no está, JavaScripr retorna “indefinido”. Esto es muy bueno, pero puede enmascarar error si no se es cuidadoso, asi que, ¡ten mucho cuidado al teclear!.

Añadir metodos se hace de la misma forma, — aquí hay un ejemplo:

triangulo.getArea    =   function ( a, b, c ) {
  // Devuelve el area de un triángulo utilizando la fórmula de Herón
  var semiperimetro   =   (a + b + c) / 2;
  var valor           =   semiperimetro * (semiperimetro - a) *
                                (semiperimetro - b) * (semiperimetro - c);
  return Math.sqrt( valor );
};      // Tener en cuenta el punto y coma aquí, es obligatorio.

Si estás pensando que esto se parece un montón a definir una función, has acertado: simplemente hemos dejado de lado el nombre de la función. JavaScript posee el concepto de funciones anónimas, que no tienen un nombre propio, y que son almacenadas en variables como cualquier otro valor. En este código, hemos creado una función anónima, y la hemos almacenado en el objeto triangulo, propiedad getArea. Por tanto, este objeto llevará consigo esta función, como puede llevar cualquier otra propiedad.

Auto-referencias

Uno de los logros de la creación del objeto triangulo es la creación de un vínculo entre los datos del triángulo y las acciones que se pueden llevar a cabo sobre esos datos. Aunque esto todavía no lo hemos logrado, podemos ver claramente que el método triangulo.getArea() sigue necesitando que los datos de los lados del triángulo se le pasen en la ejecución, resultando un código como este:

triangulo.getArea( triangulo.ladoA, triangulo.ladoB, triangulo.ladoC );

Evidentemente, esto es mejor que el código que hicimos al inicio de este artículo, ya que expresa una relación entre los datos y las acciones. Pero de todas formas, esa relación, significa que no deberíamos tener que decir al método cuáles son los valores sobre los que debería trabajar. Debería ser capaz de recoger los datos del propio objeto en el cual está definido, y utilizar esos datos sin tener que declarar ningún parámetro.

El secreto reside en la palabra reservada this, que podemos usar dentro de la definición de un método para referirnos a otras propiedades y métodos del mismo objeto. Asi que, reescribiendo el método getArea para usar la palabra reservada this, nos encontramos con el siguiente código:

triangulo.getArea    =   function () {
  // Devuelve el área de un triángulo utilizando la fórmula de Herón
  var semiperimetro   =   (this.ladoA + this.ladoB + this.ladoC) / 2;
  var valor           =   semiperimetro * (semiperimetro - this.ladoA) *
                                (semiperimetro - this.ladoB) * (semiperimetro - this.ladoC);
  return Math.sqrt( valor );
};      // Tener en cuenta el punto y coma aquí, es obligatorio.

Como podemos ver, la palabra reservada this funciona algo así como un espejo. Cuando el método getArea es ejecutado, él echa un vistazo a si mismo para leer los valores de las propiedades ladoA, ladoB, y ladoC. Él es capaz de usar estos valores en sus cálculos, en vez de pedirlos como entrada desde fuera del método.

Nota: Se han simplificado las cosas. La palabra reservada this no siempre se refiere al objeto en el que el método ha sido definido, sino que puede cambiar según que contextos específicos. Esto puede sonar críptico, pero eso sobrepasa el alcance de este artículo. No obstante, en el contexto que estamos usando aquí, podemos estar seguros que todos los usos de this siempre se referirán al objeto triangulo.

Objetos como arrays asociativos

El operador . no es la única forma de acceder a las propiedades y métodos de un objeto; se puede acceder a ellos de una forma mas eficiente usando la notación subscript, que nos debería ser familiar, del artículo discusiones sobre arrays. En pocas palabras, se puede tratar a un objeto como un array asociativo que mapea una cadena a un valor, de la misma forma que un array típico asocia a un número un valor. Utilizando esa notación, podemos reescribir el objeto triangulo de otra forma:

var triangulo = new Object();
triangulo['ladoA']   =   3;
triangulo['ladoB']   =   4;
triangulo['ladoC']   =   5;
triangulo['getArea'] =   function ( a, b, c ) {
  // Devuelve el área de un triángulo utilizando la fórmula de Herón
  var semiperimetro =   (a + b + c) / 2;
  var valor         =   semiperimetro * (semiperimetro - a) * (semiperimetro - b) * (semiperimetro - c);
  return Math.sqrt( valor );
};      // Tener en cuenta el punto y coma aquí, es obligatorio.

Después de un primer vistazo, esto puede parece superfluo. ¿Porqué no usar el operador .? Las ventajas de esta nueva sintaxis es que los nombres de las propiedades no están prefijados en el código. Podemos usar variables para especificar los nombres de las propiedades, lo que significa que podemos construir acciones muy flexibles que hagan cosas diferentes, basándose en el contexto. Por ejemplo, podríamos construir una función que comparase dos objetos para ver si entre ellos se comparte una propiedad común:

function isPropertyShared( objetoA, objetoB, nombrePropiedad ) {
  if (
     typeof objetoA[ nombrePropiedad ] !== undefined
     &&
     typeof objetoB[ nombrePropiedad ] !== undefined
     ) {
         alert("Ambos objetos tienen una propiedad llamada " + nombrePropiedad + "!");
       }
}

Esta función sería simplemente imposible de escribir de una forma genérica usando el operador ., al tener que escribir explícitamente el nombre de las propiedades a testear en el código del programa. Así que, seguro que utilizaremos esta notación muy a menudo.

Nota: Un array asociativo se llama “hash” en Perl, “hashtable” en C#, “map” en C++, “hashmap” en Java, “dictionary” en Python, etc. Es un concepto de programación fundamental, y muy potente, por tanto, probablemente hayamos oído hablar acerca de él bajo algún nombre igual o diferente a estos.

El objeto literal

Echemos un vistazo a este código que seguramente nos es muy familiar:

alert("Hola Mundo");

Podemos identificar alert como una función que está siendo llamada con un solo argumento: la cadena “Hola Mundo”. Lo hay que tener en cuenta aquí es que no es necesario escribir:

var cadenaTemporal = "Hola Mundo";
alert(cadenaTemporal);

JavaScript simplemente entiende que cualquier cosa contenida entre dos pares de dobles comillas (" ") debe ser tratado como una cadena, y lleva a cabo la magia necesaria para que eso funcione, se escriba donde se escriba. La cadena es creada y pasada correctamente a la función, todo en uno. Formalmente, "Hola Mundo" es una cadena literal; hemos pasado totalmente de todo lo que se necesita para crear la cadena.

JavaScript tiene una sintaxis similar para los “objetos literales”, que nos permiten crear nuestros propios objetos sin tener que seguir una sintaxis general. Reescribamos una vez más el objeto triangulo, esta vez como un objeto literal:

var triangulo = {
  ladoA:      3,
  ladoB:      4,
  ladoC:      5,
  getArea:    function ( a, b, c ) {
    // Devuelve el área de un triángulo utilizando la fórmula de Herón
    var semiperimetro =   (a + b + c) / 2;
    var valor         =   semiperimetro * (semiperimetro - a) * (semiperimetro - b) * (semiperimetro - c);
    return Math.sqrt( valor );
  }
};

La sintaxis es clara: el objeto literal utiliza llaves para marcar el inicio y el fin del objeto, que contiene un numero arbitrario de pares “nombrePropiedad: valorPropiedad” separados por comas. Esto hace que sea más fácil construir estructuras para usar en nuestros programas, sin tener que repetir constantemente el nombre del objeto en cada línea.

Una de la cosas a revisar, es que es un error muy común el poner una coma después de la última propiedad en la lista de propiedades del objeto literal (en este caso, después de la definición de getArea). Solamente hay que poner comas entre propiedades — una coma extra al final causará errores. Especialmente, si más adelante en el código se insertan o se eliminan propiedades, por tanto, habrá que ser especialmente cuidadoso sobre poner las comas en los sitios correctos

Resumen — hay mucho más que aprender

Realmente, esto solo ha sido arañar la superficie de las capacidades y limitaciones de los objetos en JavaScript. Después de leer esto, deberíamos sentirnos a gusto creando nuestros propios objetos, añadiendo propiedades y métodos, y usándolos de forma auto-referencial. Hay mucho más fuera de aquí, pero ninguno de ellos es esencial. El sentido de este artículo es iniciar el camino, y proveer las herramientas necesarias para entender el código necesario para profundizar en el tema.

Otras lecturas

Ejercicios

  • ¿Cuando se debería utilizar la notacion subscript en vez del operador . cuando referenciamos las propiedades de un objeto?
  • ¿Como se puede referenciar a si mismo un objeto? ¿Porqué esto es importante?
  • ¿Que es un objeto literal? Cuando creamos un objeto literal, ¿donde van las comas?
  • Hemos creado un objeto que representa un triángulo, y que calcula su área. Habría que hacer lo mismo para un rectángulo. Y utilizar this en el método getArea del rectángulo para evitar tener que pasar sus datos innecesariamente.

Acerca del autor

Foto del autor del artículo, Mike West

Mike West es un estudiante de filosofía habilmente disfrazado como un experimentado y exitoso desarrollador web. Él ha estado trabajando en la web durante una década, más recientemente en el equipo responsable de la construcción del sitio de noticias de Yahoo Europa.

Después de abandonar las llanuras de suburbios de Texas en 2005, Mike se estableció en Munich, Alemania, donde lidia (cada vez menos) cada dia con el idioma. mikewest.org es su página personal en la web, donde, despacio, reune sus escritos y enlaces juntos para la posteridad. Él mantiene su código en GitHub.

This article is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported license.

Comments

The forum archive of this article is still available on My Opera.

No new comments accepted.