Funciones Constructoras en JavaScript

24. January 2012 18:14 by Oscar.SS in Desarrollo Web  //  Tags:   //   Comments (0)

En un artículo anterior vimos una pequeña introducción de los objetos en JavaScript en el que se mostraron algunas características esenciales de los objetos en sí mismos. Continuamos ahora hablando de objetos y profundizando un poco en algunos conceptos.

Como ya sabemos, en la programación orientada a objetos (POO) es habitual tratar con los conceptos de encapsulación, herencia y polimorfismo. Puede que haya autores, y por qué no, también lectores, que tengan sus reservas a la hora de considerar JavaScript como un lenguaje totalmente orientado a objetos. La intención de este artículo no es entrar en este tipo de polémicas, pero lo que si podemos considerar es que JavaScript, como hemos visto anteriormente, soporta objetos y hace una simulación muy personal de otros conceptos como clases o herencia

JavaScript no tiene una notación formal de clase y recurre a las funciones constructoras para este fin. Mencionar también que JavaScript utiliza los prototipos de los objetos para propagar la herencia, algo que sin duda cuesta entender al principio y al que dedicaremos un artículo independiente más adelante.


Función constructora

Una función constructora es una función normal y corriente de JavaScript que se utiliza para definir una especie de plantilla para nuestros objetos personalizados. Veamos un ejemplo.

        function Cliente(nombre, fecha, direccion) {

            
this._nombre nombre;
            this
._fechaNacimiento fecha;
            this
._direccion direccion;
        
}

Como podemos observar, se trata de una típica función de JavaScript que admite una serie de parámetros de entrada aunque estos no son obligatorios en absoluto. La única particularidad de esta función es que se utiliza la palabra reservada this de JavaScript para definir una serie de propiedades (también podrán ser métodos) que formarán parte de nuestros objetos personalizados.

En la siguiente ilustración vemos cómo podemos utilizar esta función constructora para crear instancias de nuestros objetos personalizados

El operador new utilizado junto a una función de JavaScript es lo que nos permite obtener un objeto constructor o función constructora. Lo que sucede por debajo es que new primeramente crea un objeto sin propiedades y posteriormente llama a la función pasándole el nuevo objeto como valor de la palabra reservada this. Finalmente, la función nos devuelve un nuevo objeto con las propiedades y métodos definidos dentro de la constructora. Como se aprecia en el intellisense de la imagen observamos que el nuevo objeto miCliente tiene todas las propiedades definidas anteriormente dentro del constructor.

Como hemos comentado no es necesario que el constructor tome parámetros, podemos crear una plantilla en blanco e ir rellenando los objetos con datos cuando lo necesitemos.

        //Constructor vacio
        
function Cliente() {

            
this._nombre "";
            this
._fechaNacimiento = null;
            this
._direccion "";
        
}

        
//Creamos el objeto y le asignamos valores
        
var cliente = new Cliente();
        
cliente._nombre "Cristina Rodriguez";
        
cliente._fechaNacimiento = new Date(1987325);
        
cliente._direccion "Plaza Bilbao 25";

Cuando hablamos de los objetos en JavaScript vimos que se podían definir objetos por medio de la notación JSON. Pues bien, también podemos definir objetos por medio de una función que devuelva un literal de objeto. En este caso, la función constructora hace de envoltorio para el código JSON de definición del objeto permitiéndonos reutilizar el código para crear distintas instancias del mismo.

        function Cliente(nombre, fecha, direccion) {

            
return {
                _nombre: nombre,
                _fechaNacimiento: fecha,
                _direccion: direccion
            }
;
        
}

Debemos tener en cuenta que siempre que utilicemos un return dentro de una función constructora, el objeto devuelto, ocultará al resto de miembros que intentamos definir. No importa si la función devuelve un objeto literal, una cadena, un número, etc. Esto siempre ocultará a los demás miembros públicos que hayamos definido.

        function Cliente(nombre, fecha, direccion, email) {

            
this._email email;

            return 
{
                _nombre: nombre,
                _fechaNacimiento: fecha,
                _direccion: direccion
            }
;
        
}

En este ejemplo cabría esperar que un objeto creado a partir de esta función constructora tuviera 4 propiedades públicas. Pero no es así, la propiedad email queda ocultada por el objeto que se devuelve con return, por lo que obtendríamos un objeto idéntico al del ejemplo anterior.

 

Miembros de instancia

Las propiedades y métodos definidos dentro de la función constructora se pueden denominar miembros de instancia dado que cada objeto creado a partir de la función constructora guardará su propia copia de los miembros definidos. Veamos ahora como podemos diferenciar entre miembros de instancia públicos y privados.

Hace unos instantes hemos definido una serie de propiedades públicas en nuestros objetos por medio de variables JavaScript y la palabra reservada this. Para definir métodos públicos procederemos de la misma forma con la salvedad de que utilizaremos una función de JavaScript. También podemos definir propiedades y métodos privados al objeto simplemente definiendo variables y funciones JavaScript dentro de la función constructora utilizando el var de toda la vida. Es decir, para definir miembros públicos utilizaremos this y para los miembros privados utilizaremos var.

Veamos un ejemplo de código simplificado para clarificar todo esto. 

        function Cliente(nombre, fecha, direccion) {

            
//Propiedades privadas
            
var edad;

            
//Métodos privados
            
var calcularEdad = function () {
                
var actual = new Date().getYear();
                var 
nacimiento fecha.getYear();

                if 
(actual <nacimiento)
                    edad 
"Error: no se ha podido calcular";
                else
                    
edad actual - nacimiento;
            
}

            
//Propiedades públicas
            
this._nombre nombre;
            this
._fechaNacimiento fecha;
            this
._edad edad;
            this
._direccion direccion;

            
//Métodos públicos
            
this._presentarse = function () {
                calcularEdad()
;

                document
.write(
                    
"Hola, mi nombre es " this._nombre + " y tengo " this._edad + " años."
                    
);
            
}
        }

Existen autores, como Douglas Crockford, que hacen una pequeña distinción entre métodos públicos y métodos privilegiados. Esta distinción se basa en el hecho de que existe otra forma de definir métodos públicos en los objetos por medio del prototipo de la función constructora. Ya hemos comentado que hablaremos de los prototipos en otro artículo cuando hablemos también de la herencia.

Pero para los impacientes les adelanto que con métodos privilegiados se refiere precisamente a los métodos definidos dentro del cuerpo de la función con la palabra reservada this, dado que estos métodos tienen el privilegio de tener acceso a las variables y métodos privados. Mientras que los métodos definidos por medio del prototipo de la función constructora no tendrán nunca este acceso o privilegio y son denominados simplemente métodos públicos. Podéis leer sobre esta original idea en este artículo escrito por el propio Crockford.

Creo que es importante mencionar también que desde un método privado no tendremos acceso directo a miembros públicos. Esto es así porque como hemos comentado anteriormente this hace referencia al objeto devuelto por la función constructora y para la función privada este objeto se encuentra fuera de ámbito. De hecho, existe una solución a este aparente inconveniente que pasa por utilizar una potente característica de JavaScript, los closures, que estudiaremos a fondo en otra ocasión. Veamos ahora que sucede si intentamos el acceso directo.

        function Constructor(msjPrivado, msjPublico) {

            
var propiedadPrivada msjPrivado;
            this
.propiedadPublica msjPublico;

            var 
metodoPrivado = function () {
                
alert(propiedadPrivada);
                alert
(this.propiedadPublica);
            
};

            this
.metodoPublico = function () {
                metodoPrivado()
;
            
};
        
}

        
var obj = new Constructor("mensaje privado""mensaje público");
        
obj.metodoPublico();

Cuando ejecutemos este código recibiremos dos alertas, aunque una de ellas, la que intenta mostrar el valor de la propiedad pública nos notificará un undefined. Es aún peor si intentamos acceder a la propiedad pública sin la sentencia this, directamente tendremos un error del tipo variable no definida. 

 

Miembros estáticos

Los miembros estáticos o también llamados miembros de clase son aquellos estados o comportamientos comunes a todas las instancias de la clase. En estos casos puede tener mucho más sentido no definirlos dentro de la función constructora, dado que todas las instancias de los objetos creadas a partir de ella contendrán una copia de estos miembros que son comunes a todos los objetos creados. Supongamos que en nuestro ejemplo Cliente queremos tener una propiedad que almacene el IVA que se les va aplicar a todos nuestros clientes sin excepción. 

        function Cliente() {
            
            
//Definimos los miembros de instancia...   
        
}


        
//Definimos una propiedad estática
        
Cliente.IVA 18;

        
//En otro punto del código hacemos uso del IVA
        
var total neto + (neto * (Cliente.IVA / 100));

Igualmente, si tenemos un método que devuelva siempre el mismo objeto o valor, deberíamos definirlo a nivel de la función constructora. Este podría ser el caso de un método que devuelva una instancia de inicialización con valores por defecto para nuestro objeto Cliente.

Si es la primera vez que el lector se asoma a la programación de objetos con JavaScript puede resultarle extraño este proceder pero muchos objetos nativos de JavaScript siguen este criterio. Por ejemplo, el objeto Number utiliza una propiedad estática que devuelve el mayor número posible en JavaScript, Number.MAX_VALUE. Y el objeto Date utiliza un método estático para analizar una fecha en formato cadena y devolver su representación en milisegundos desde una fecha siempre constante en JavaScript, Date.parse(string)

 

Comprobar la función constructora de un objeto

Todos los objetos de JavaScript, ya sean nativos o de usuario, tienen una propiedad constructor que heredan del objeto genérico Object, la cual hace referencia a la función constructora que inicializa el objeto lo que en principio (ahora veremos por qué digo esto) nos permite determinar la función constructora de un objeto, y casi por extensión, la clase de éste.

        function Cliente() {
            
            
//Definición de miembros de Cliente...
        
}

        
var unCliente = new Cliente();

        if 
(unCliente.constructor == Cliente) {
            
            
//Hacer algo con el objeto unCliente
        
}

Por otro lado también podríamos utilizar el operador instanceof para determinar la constructora de un objeto pero con algunas diferencias. El operador instanceof, a diferencia del anterior, comprueba la jerarquía del objeto, por lo tanto podríamos preguntar directamente sobre el objeto padre con idénticos resultados.

        unCliente instanceof Cliente // Devuelve true
        
unCliente instanceof Object // Devuelve true

Lamentablemente en JavaScript nada es tan sencillo como parece. Las cosas se complican cuando hablamos de modificar el prototipo de un objeto y la propiedad constructor parece perder la referencia a la función constructora. Pero todo esto es harina de otro costal y se sale por completo de la intención de este artículo. Recomiendo al lector que lea Constructors considered mildly confusing para comprender bien el comportamiento de la propiedad constructor y del operador instanceof.

Y para complicar más las cosas, también tenemos a nuestra disposición el operador unitario typeof. De nuevo, recomiendo a los lectores interesados en profundizar en estos temas los artículos The Secret Life of JavaScript PrimitivesFixing the JavaScript typeof operator.

Recent Comments

Comment RSS

Month List