martes, 13 de diciembre de 2011
Blogger invitado
Óscar Sotorrío Sánchez
Desarrollador independiente, MCP C#
Eterno aprendiz en esto de las tecnologías .NET y en especial con ASP.NET. Admirador de la filosofía de Internet y entusiasta de los nuevos modelos de negocio que rigen este mundillo.Blog: oscarsotorrio.com
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 sí 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 ilustración lateral 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 vacío
function Cliente() {
this._nombre;
this._fechaNacimiento;
this._direccion;
}
//Creamos el objeto y le asignamos valores
var cliente = new Cliente();
cliente._nombre = "Cristina Rodriguez";
cliente._fechaNacimiento = new Date(1987, 3, 25);
cliente._direccion = "Plaza Bilbao 25";
Cuando hablamos de los objetos en JavaScript, vimos que se podían definir 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 oculta 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 cómo 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 qué 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 propiedadconstructor
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 // es true
unCliente instanceof Object // es 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 el operador instanceof
.Y para complicar más la cosa, 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 Primitives y Fixing the JavaScript typeof operator.Autor: Óscar Sotorrío Sánchez
Publicado en Variable not found
5 Comentarios:
Son tan geniales tus post!
Muchas GRACIAS!!!!
Hola Óscar, comentabas en este artículo que hablarías de la herencia en otro artículo.
Publicaste dicho artículo? He estado mirando y no lo he encontrado.
Me puedes decir el enlace?
Saludos y gracias!
Hola!
Lo siento, pero me temo que al final ese post no llegó a publicarse :(
Pero bueno, hay por la red bastante información sobre el tema, seguro que no tienes problema en localizarla.
Un saludo!
Mil gracias, muy bien explicado.
Soy nuevo en esto pero te agradezco tu publicación... Podrías ayudarme con los métodos fuera de las funciones constructoras..? Gracias de antemano😉
Enviar un nuevo comentario