Programación en JavaScript/OOP

De Wikilibros, la colección de libros de texto de contenido libre.
← FuncionesJS Clases y objetos ClasesJS →


Dentro de los lenguajes actuales, que provienen en su mayoría de los primeros lenguajes estructurados como ALGOL o BCPL, la capacidad de crear funciones para reutilizarlas de diversas formas, ya sea dentro del mismo módulo del programa o en distintos módulos, ha sido uno de los fundamentos de la programación de sistemas informáticos. Sin embargo, este paradigma se quedaba corto para nuevas situaciones que iban surgiendo con el tiempo, como la programación de videojuegos o el 3D y la inteligencia artificial. A finales de los años 70 se comenzó a teorizar sobre lenguajes de programación que utilizasen entidades independientes que fueran autocontenidas para realizar las aplicaciones. Como ya dijimos anteriormente, un programa se compone de código y datos. los objetos son unidades que contienen su propio código y datos para su propio auto-funcionamiento. Podemos decir que son programas dentro de programas.

Dicho así, podemos darnos cuenta de que los objetos pueden ser utilizados como variables, para nuestro propio uso. Pero no podemos definir variables de objeto sin poder darles una forma. La forma de los objetos son los datos (propiedades) y código (funciones) que contiene el objeto. A esto se le denomina clase de objeto. Para definir clases en JavaScript, lo hacemos por medio de funciones, como esta:

   function Persona(nombre) {
           this.nombre = nombre;
           this.color_pelo = 'negro';
           this.peso = 75;
           this.altura = 165;
           this.sexo = 'varón';
           this.edad= 26;
   }

Vamos a fijarnos bien como se estructura esta función. Se le llama constructor de la clase, y en ella definimos los datos de la clase, los que vamos a poder utilizar al crear objetos con ella. Nótese el uso de la palabra reservada this. Esta palabra sirve para identificar al objeto en sí mismo dentro de la definición de la clase. Cuando escribimos

   this.peso = 75;

estamos creando la propiedad "peso" de la clase "Persona". Cuando creamos una propiedad en el constructor y le damos un valor, como en este caso, estamos asignándole un valor por defecto. Todos los objetos creados con este constructor contendrán una propiedad "peso" con ese valor inicial, aunque luego podremos cambiarlo al usar el objeto. Para definir un objeto de esta clase, sólo tendríamos que hacer esto:

   var hombre = new Persona('Pepe');

Aquí hemos definido el objeto "hombre", que contendrá todas las propiedades definidas en la función-clase "Persona". Si queremos cambiar su valor, sólo tenemos que hacer algo como esto:

   hombre.peso = 80;

De esta forma, el dato definido para este objeto cambia. Pero si hemos definido más objetos de tipo Persona, cada uno de ellos contendrá las mismas propiedades pero con valores distintos. Ningún objeto tiene el mismo valor que otro objeto de la misma clase a no ser que nosotros se lo asignemos explícitamente.

   var mujer = new Persona('Ana');
   mujer.peso = 67;
   mujer.sexo = 'mujer';

En este caso hemos hecho lo mismo, pero le indicamos su propio peso, independiente del de la variable "hombre". Así, podemos tener tantos objetos de la misma clase como queramos para realizar las operaciones que sean pertinentes. Una última cosa sobre los constructores: como podemos ver, podemos pasarle parámetros, que podemos convertir en los valores de las propiedades de los objetos de esa clase.

Creación de funciones miembro[editar]

Hasta ahora hemos visto como crear propiedades de las clases, pero necesitamos crear código en ese objeto que utilice las propiedades que hemos creado en el constructor. Para crear una función miembro, debemos indicarlo en la propia función de construcción:

    function Persona(nombre) {
            this.nombre = nombre;
            this.color_pelo = 'negro';
            this.peso = 75;
            this.altura = 165;
            this.sexo = 'varón';
            this.dormir = dormir; // Nueva función miembro
    }

Y ahora definimos la función dormir:

    function dormir() {
            alert(this.nombre + ' está durmiendo');
    }

Fijémonos en la función. Tiene una forma bastante normal. Lo único especial que hemos hecho es añadir la línea

    this.dormir = dormir;

al constructor, con lo que hemos asignado la función dormir como si fuera una propiedad. Recordemos que TODO es un objeto en JavaScript, y esto incluye a las funciones. Ahora, para ejecutar este código, utilizamos el objeto anteriormente creado para ponerlo en marcha:

    hombre.dormir();

Veamos en un ejemplo todo el código que hemos generado hasta ahora:

    <html>
    <head>
    <script language="javascript">
    function Persona(nombre) {
        this.nombre = nombre;
        this.color_pelo = 'negro';
        this.peso = 75;
        this.altura = 165;
        this.sexo = 'varón';
        this.dormir = dormir;
    }
    
    function dormir() {
            alert(this.nombre + ' está durmiendo');
    }
    </script>
    </head>
    
    <body>
    <form>
    </form>
    <script>
        var hombre = new Persona('Pepe');
        hombre.dormir();
    </script>
    </body>
    </html>

Como resultado, nos mostrará el mensaje "Pepe está durmiendo". Como vemos, podemos usar las propiedades de los objetos dentro de las funciones miembro, aunque también podríamos construir la misma función de otra manera:

    function dormir() {
        with (this)
            alert(nombre + ' está durmiendo');
    }

with es una palabra reservada de JavaScript que permite coger una variable de objeto como this y permite utilizar sus miembros como si fueran variables independientes. Pero tiene sus restricciones: estos nombres abreviados sólo se pueden utilizar dentro del ámbito de with (que si tiene varias líneas, estas deben estar contenidas entre llaves, como for, if, etc...), y además, se pueden confundir fácilmente con variables locales a la función o globales del programa, con lo cual particularmente no recomendamos el uso de with, ya que puede dar lugar a fallos de ejecución difíciles de tratar si no se tienen en cuenta estas restricciones. Se aconseja usar la forma this.nombre. También se recomienda crear cada clase en un archivo diferente para que no haya confusiones de nombres, sobre todo de funciones miembro.

Otra manera de declarar la clase en JavaScript:

  <html>
    <head>
    <script language="javascript">
    function Persona(nombre) {
        this.nombre = nombre;
        this.color_pelo = 'negro';
        this.peso = 75;
        this.altura = 165;
        this.sexo = 'varón';
        this.dormir = function dormir(){
            alert(this.nombre + ' está durmiendo');
        }

    }
    </script>
    </head>
    <body>
    <form>
    </form>
    <script>
        var hombre = new Persona('Pepe');
        hombre.dormir();
    </script>
    </body>
    </html>


Con este ejemplo se obtiene el mismo resultado que el anterior pero el código queda un poco mas complejo. A pesar de esto ya podemos ver que a diferencia del código anterior este se encuentra encapsulado en la misma función [ function Persona(){} ]

Otro ejemplo de creación de una clase más complicado utilizando DOM estándar y JavaScript; debemos recordar que innerHTML es un método propietario de Microsoft y que desaparecerá de la especificación en un futuro muy próximo. Nótese que la propiedad texto debe ser un nodo de texto (DOMTextNode) en lugar de HTML (Cualquier interface DOM: DOMNode, DOMElement...) al vuelo como ocurriría usando innerHTML.

<html>
	<head>
	<script type="text/javascript" charset="utf-8">
	// <![CDATA[
		function CrearCapas(idcapa, info) {
			this.id 		= idcapa;
			this.texto 		= info;
			this.CrearCapa 	= function CrearCapa() {
				try {
					// Esta es la manera correcta y estándar -aunque más lenta y costosa- 
					// de generar contenido mediante el DOM:
					// Objetos DOMElement necesarios:
					var body 	= document.getElementsByTagName('body').item(0); 	// Tag <body> también se puede en forma de vector:
																		//...agName('body')[0]; pero no es compatible con Opera.
					var capa 	= document.createElement('div');					// División al vuelo
					var texto 	= document.createTextNode(this.texto);			// Texto contenido en div al vuelo
					// Establecer atributos de división:
					capa.setAttribute('id', this.id);
					capa.setAttribute('style', 'background-color:#f7f7f7; width:100px; border:#000000 2px solid;');
					// Reconstruir el árbol DOM:
					capa.appendChild(texto);
					body.appendChild(capa);
				} catch(e) {
					alert(e.name + " - " + e.message);
				}
			}
		}
	// ]]>
	</script>
	</head>

	<body>
		<script type="text/javascript" charset="utf-8">
		// <![CDATA[
			var capa = new CrearCapas('idCapanueva', 'Hola Mundo');
			capa.CrearCapa();
		// ]]>
		</script>
	</body>
	</html>

El resultado del código anterior es una página que por medio de JavaScript crea una división (div) y le asigna atributos como ancho, alto, color, etc, y lo inserta dentro de la página al cargarse.

Creando clases usando el estándar ECMAScript 6[editar]

Con la llegada del stándar ECMAScript 6 en el 2015 se agregó azúcar sintáctica a la forma de crear las clases en JavaScript, ahora podemos crear nuestras clases de una forma más sencilla y estandarizada como lo hacen los lenguajes de POO.

Creando una clase[editar]

// Para crear una clase se usa la palabra reservada 'class'
class Persona {
    /* El método 'constructor' se utiliza para inicializar los atributos
     * de la clase.
     *
     * Observar que se pueden especificar valores por defecto a los perámetros.
     *  - Se pasa el valor por defecto 'conocido' a 'tipoSaludo'.
    */
    constructor(nombre, edad, email, tipoSaludo = 'conocido')
    {
        // Para hacer referencia a las propiedades del objeto se utiliza la
        // palabra reservada 'this'.
        this._nombre = nombre;
        this._edad = edad;
        this._email = email;
        this._tipoSaludo = tipoSaludo;
    }

    // Se pueden crear los getter con la palabra reservada 'get'.
    // Los getter sirven para obtener los valores de las propiedades
    // del objeto.
    get nombre()
    {
        return this._nombre;
    }

    // Se pueden crear los setter con la palabra reservada 'set'.
    // Los setter sirven para asignar nuevos valores a las propiedades
    // del objeto.
    set tipoSaludo(tipoSaludo) {
        this._tipoSaludo = tipoSaludo;
    }

    // Para crear un método simplemente se define su nombre
    saludar(nombre)
    {
        if (this._tipoSaludo === 'conocido')
            console.log(`Hola ${nombre}, ¿Cómo estas?`);
        else
            console.log(`Hola, mi nombre es ${this._nombre}`);
    }

    /* En algunas ocaciones se puede dar el caso de que no podemos tener
     * acceso a nuestro objeto, la solución a este inconveniente se muestra
     * y explica en este método.
    */
    mostrarme()
    {
        // Declarando una variable local y asignándole una referencia al propio
        // objeto.
        let _this = this;

        // En una función anónima no se puede acceder al propio objeto usando
        // la palabra reservada 'this' (obtenemos como salida 'undefined').
        (function () {
            console.log(this);
        })();

        // Una solución es declarar una variable y asignarle una referencia
        // al objeto como se hace al inicio del método.
        (function () {
            console.log(_this);
        })();
        
        // Esta es la manera correcta y elegante de acceder a nuestro objeto.
        ((e) => {
            console.log(this);
        })();
    }
    
    // Los métodos estáticos se declaran usando la palabra reservada 'static'.
    static girar()
    {
        console.log('Girando!');
    }   
}

// Para crear una instancia de la clase 'Persona' se usa la palabra reservada
// 'new'. Recordar que el cuarto parámetro es opcional, por lo que al no pasarle
// valor tomara por defecto el especificado en el método 'constructor' de la
// clase.
var p = new Persona('Juan', 32, 'juan@mail.com');

// Llamando a uno de sus métodos.
p.saludar('Ana');

// Cambiando el valor del atributo 'tipoSaludo' usando el setter tipoSaludo
p.tipoSaludo = 'otro';
p.saludar();

// Obtenieno el valor del atributo 'nombre' usando el getter nombre
console.log(p.nombre);

// Ejemplo del acceso al propio objeto y la mejor forma de hacerlo, en
// circunstancias como: los eventos, funciones anónimas, uso de JQuery dentro
// del método, etc.
p.mostrarme();

// Un método estático no necesita de una instacia de clase para ser invocado.
Persona.girar();
← FuncionesJS Clases y objetos ClasesJS →