Propiedad prototype: Accediendo al prototipo del objeto para modificar la clase

En el primer tema hablamos de como crear una clase coche y luego usábamos una función que denominábamos modificadora para crear nuevas instancias de esa clase y, al mismo tiempo, agregar propiedades, métodos o reescribirlos. Pero aunque los nuevos objetos creados con esa función seguián perteneciendo a la clase coche, las modificaciones se realizaban a nivel de objeto. Por ejemplo, podíamos crear un objeto de esa clase sin usar la función modificadora que no llevaba incorporadas las modificaciones.

Ahora el problema es que no queremos que esas modificaciones se produzcan a nivel de objeto sino a nivel de clase, de tal forma que las modificaciones afecten a todas las instancias de esa clase ya creadas y a las futuras que se puedan crear. La propiedad prototype es accesible para todos los objetos y nos da una referencia a la clase con la que fue creada. Con el prototipo podemos modificar las propiedades y métodos de una clase, de tal forma que todos los objetos creados anterior y posteriormente incorporarán esas modificaciones.

Veámos esto en acción. Recordamos como era la clase coche:

 function coche(unaMarca, unModelo, unColor) {
     this.marca = unaMarca;
     this.modelo = unModelo;
     this.color = unColor;

     this.seleccionarMarca = function seleccionarMarca(otraMarca){
         this.marca = otraMarca
     };

     this.seleccionarModelo = function seleccionarModelo(otroModelo){
         this.modelo = otroModelo
     };

     this.seleccionarColor = function seleccionarColor(otroColor){
         this.color = otroColor
     };

     this.verComoEs = function verComoEs(){
         return "Marca: " + this.marca + "<br />" +
         "Modelo: " + this.modelo +  "<br />" +
         "Color: " + this.color;
     };

 }
    

Con este ejemplo de la clase coche creamos una instancia de objeto que llamamos primerCoche.

<b><i>"Así es..."</i></b>
<div id="div1" class="verde"></div>
<script>
    var primerCoche = new coche("Volkswagen", "Golf", "Rojo");
    document.getElementById("div1").innerHTML = primerCoche.verComoEs();
</script>
Actualización NOV 2012: Con los últimos cambios he decidido que todos los scripts vinculados desde archivos externos se carguen asíncronamente. Por lo tanto el código del script anterior y el resto de esta página que necesiten el archivo objetos.js cargado, los he tenido que traspasar al window.onload de esta página, momento en el cual estará disponible el constructor ubicado en el archivo vinculado objetos.js.

Ejemplo:

"Así es el primerCoche:"

Luego accedemos al prototipo de la clase con coche.prototype para modificar cosas. Ponemos una nueva propiedad matricula (la incluímos en la clase, no en el objeto como hacíamos con la función modificadora), y dos nuevos métodos ponerMatricula() y verMatricula().

<b><i>"Modificamos..."</i></b>
<div id="div2" class="verde"></div>
<script>
    coche.prototype.matricula = "";
    coche.prototype.verMatricula = function verMatricula() {
        return this.matricula;
    }
    coche.prototype.ponerMatricula = function ponerMatricula(unaMatricula) {
        this.matricula = unaMatricula;
    }
    primerCoche.ponerMatricula("ABC1234");
    document.getElementById("div2").innerHTML = primerCoche.verComoEs() + "<br />" +
    "Matrícula: " + primerCoche.verMatricula();
</script>
    

Ejemplo:

"Modificamos la clase coche para añadir propiedades y métodos:"

De ahora en adelante todos los objetos instanciados de la clase coche ya tienen esa nueva propiedad y métodos. Por ejemplo, podemos crear un nuevo objeto segundoCoche:

<b><i>"Este es..."</i></b>
<div id="div3" class="verde"></div>
<script>
    var miSegundoCoche = new coche("Fiat", "Punto", "Azul");
    miSegundoCoche.ponerMatricula("MAT0001");
    document.getElementById("div3").innerHTML = miSegundoCoche.verComoEs() + "<br />" +
    "Matrícula: " + miSegundoCoche.verMatricula();
</script>
    

Ejemplo:

"Este es un segundoCoche de la clase coche modificada:"

Modificando clases intrínsecas de JavaScript con prototype.

Los objetos intrínsecos de JavaScript como String, Number o Array tienen una estructura y comportamientos definidos. Pero a veces podemos necesitar que se comporten de otra forma. ¿Es posible modificar estas clases?. Veámos esto con un ejemplo.

El objeto Number de JavaScript nos sirve para manipular números reales. La forma de presentar un número es con un punto para la separación de decimales y sin separar los grupos de millares. El método toFixed() del objeto Number nos permite redondear el número de decimales, en este caso a dos para un tipo de moneda como Euros.

<b><i>"Los números del objeto Number de JavaScript:</b>"</i></b>
<div id="div4" class="verde"></div>
<script>
    var euros = 24173.56789;
    var eurosToFixed = euros.toFixed(2);
    document.getElementById("div4").innerHTML = "Euros: " + euros + "<br />" +
    "Euros redondeado 2 decimales con <i>toFixed</i>: "+ eurosToFixed + "<br />";
</script>
    

Ejemplo:

"Los números del objeto Number de JavaScript:"

Pero pongamos el caso de que, además del redondeo, se presente el número con formato de moneda, algo así como 24.173,57 €. Podríamos reescribir el método toFixed() de Number para lo cual usamos la propiedad prototype. Este ejemplo no pretende ser un algoritmo para uso final de presentar formatos de moneda, pues seguro que hay cosas más simples y eficientes ya hechas, sino dejar claro que con prototype podemos modificar una clase intrínseca de JavaScript. Por lo tanto no explicamos los pormenores, aunque se acompañan algunos comentarios en el código.

<p><b><i>"El número ..."</i></b></p>
<div id="div5" class="verde"></div>
<p><b><i>"Con estos cuadros...:"</i></b></p>
<label>Número: <input id="cuadro-numero" type="text" value="24173.56789" /></label><br />
<label>Redondear a número decimales: <input id="numero-decimales" type="text" value="2"
size="2" maxlength="2" /></label><br />
<label>Caracter separador de miles: <input id="separa-miles" type="text" value="."
size="1" maxlength="1" /></label><br />
<label>Caracter separador de decimales: <input id="separa-decimales" type="text" value=","
size="1" maxlength="1" /></label><br />
<label>Símbolo de moneda: <input id="simbolo-moneda" type="text" value="€"
size="6" /></label>
<input type="button" value="ver formato" onclick="redondea()" />
<div id="div6" class="verde"></div>
<script>
    //Aquí se usa prototype para modificar el método toFixed() de la clase Number.
    //En definitiva se trata de formatear el número real con un determinado número
    //de decimales, caracteres separadores de miles y de decimal y el símbolo final
    //de moneda.
    //No podemos usar toFixed() para el redondeo porque estamos reescribiéndolo y
    //se produciría una llamada a sí mismo que volcaría la pila. Por lo tanto hay
    //que implementar un algoritmo de redondeo completo.
    Number.prototype.toFixed = function toFixed(numeroDecimales, separaMiles,
               separaDecimales, simboloMoneda) {
        //Dividimos el número en parte entera y decimal
        var parteEntera = parseInt(this);
        var parteDecimal = this - parteEntera;
        //Esta parte hace el redondeo al número de decimales
        var cadDecimal = "";
        if (parteDecimal > 0) {
            var potencia = Math.pow(10, numeroDecimales);
            var decimales = parteDecimal * potencia;
            var decimalesParteEntera = parseInt(decimales);
            var decimalesParteDecimal = decimales - decimalesParteEntera;
            if (decimalesParteDecimal >= 0.5) decimalesParteEntera++;
            cadDecimal = decimalesParteEntera.toString();
            if (cadDecimal.length > numeroDecimales) {
                cadDecimal = "";
                for (var i=0; i<numeroDecimales; i++){
                    cadDecimal += "0";
                }
                parteEntera++;
            } else if (cadDecimal.length < numeroDecimales) {
                for (var i=cadDecimal.length; i<numeroDecimales; i++) {
                    cadDecimal = "0" + cadDecimal;
                }
            }
        } else if (parteDecimal == 0) {
            for (var i=0; i<numeroDecimales; i++){
                cadDecimal += "0";
            }
        }
        //Esta parte formatea separadores de miles sobre la parte entera
        var cad1 = parteEntera.toString();
        var cad2 = "";
        for (var i=(cad1.length-1); i>-1; i=i-3) {
            var cad3 = cad1.substring(i-2, i+1);
            if (cad2 != ""){
                cad2 = cad3 + separaMiles + cad2;
            } else {
                cad2 = cad3;
            }

         }
         //Devolvemos la cadena de número formateada 
         return cad2 + separaDecimales + cadDecimal + " " + simboloMoneda;
    }
    //Esta es una función que aplica el nuevo toFixed() al valor de los cuadros <input>.
    //Vea como aplicamos el constructor del objeto con new Number() para dejar claro
    //que lo que esperamos en el <input> es un número.
    function redondea(){
        var numeroInput = document.getElementById("cuadro-numero").value;
        var euros = new Number(numeroInput); //creación explícita de un objeto de Number.
        var numDec = document.getElementById("numero-decimales").value;
        var sepMil = document.getElementById("separa-miles").value;
        var sepDec = document.getElementById("separa-decimales").value;
        var simbol = document.getElementById("simbolo-moneda").value;
        document.getElementById("div6").innerHTML = euros.toFixed(numDec, sepMil, sepDec, simbol);
    }
    //Esto es un ejemplo directo donde se observa que en JavaScript se puede construir
    //un número con new Number (creación explícita) o bien de forma literal (creación
    //implícita). Igual que lo que pasa con la clase String.
    var moneda = 3456207.677019; //creación implícita de un objeto de Number.
    moneda = moneda.toFixed(2, ".", ",", "€");
    document.getElementById("div5").innerHTML = moneda;

    //Con esto ejecutamos los valores iniciales de los cuadros <input> de ejemplo.
    redondea();
</script>

    

Y aquí pueve ver el resultado:

Ejemplo:

"El número 3456207.677019 se redondea y formatea con el método toFixed(2, ".", ",", "€"), quedando así:"

"Con estos cuadros puede probar otros valores:"





En resumen, la propiedad prototype nos permite modificar la plantilla de un objeto a nuestro gusto. Es realmente lo que significa, modificamos el prototipo de la plantilla para tener un nuevo molde y hacer nuevos objetos modificados.


Pero aún hay un tema diferente: la herencia de los objetos que veremos en el siguiente tema, donde prototype tiene una especial relevancia.