Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 13 de diciembre de 2011

Blogger invitado

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
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 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.

Uso de la función constructora, y ayuda de IntellisenseEn 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 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 // 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:

elkin21 dijo...

Son tan geniales tus post!

Muchas GRACIAS!!!!

Alex MM dijo...

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!

José María Aguilar dijo...

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!

Unknown dijo...

Mil gracias, muy bien explicado.

Unknown dijo...

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😉