Me parece que estás pensando Javascript como lenguaje orientado objetos según el paradigma de clases. No es así el asunto. En Javascript no hay clases. Todo es un objeto, que es como un map
(o si vienes de Python, un dictionary
) de propiedades con sus valores.
var objeto1 = {nombre : 'yo', edad : 30};
var objeto2 = {nombre : 'ella', edad : 29};
function imprimirObjeto(o) {
console.log(o);
}
imprimirObjeto(objeto1);
Lo que hay que es que:
Todo objeto tiene una propiedad oculta (el "prototipo"). Cuando trato de leer una propiedad en un objeto y este no la tiene, trata de leerla en el "prototipo". (El prototipo puede a su vez tener otro prototipo, y así...)
No se puede acceder directamente al prototipo.
Una función también es un objeto (con un prototipo especial, pero eso por ahora no nos importa).
Hay funciones que están diseñadas para trabajar como algo-así-como "constructores". Suelen empezar (por convención) con mayúscula, y no suelen invocarse explicitamente como otras funciones, sino solo con el keyword new
.
Al ejecutar
function Casa() {
this.habitaciones = 2;
...
}
var c1 = new Casa();
var c2 = new Casa();
Javascript fabrica dos nuevos objetos alamacenados en la variable c1
y c2
. Informalmente, pensamos que "instanciamos dos casas"; correcto, pero no hay que confundir el objeto Casa
(la función constructora, que es una sola) con los objetos c1
y c2
que son dos objetos creados por Casa()
y que no son copias de Casa
).
(Quizás ayude un poco a entenderlo si renombramos mentalmente Casa()
a fabricarCasa()
, y en lugar de c1 = new Casa()
imaginamos c1 = fabricarCasa()
. Así es como lo haríamos en otros lenguajes, pero en Javascript no es posible, e importa no confundirse: hay que usar new
para que una funcion constructora funcione como tal, y si nos olvidamos pasan cosas feas)
. Lo que tiene de particular esto es que
El keyword this
adentro de un "constructor" se referirá al nuevo objeto creado en la llamada con new
Un constructor tiene un atributo especial llamado prototype
(por defecto, un objeto vacío); el valor de este prototype
se asignará como prototipo de cada nuevo objeto creado.
¡Ojo! (Y esto es lo que suele confundir mucho al principio) : Casa.prototype
no es el prototipo del objeto Casa
! Es el prototipo que se asignará (no por copia, sino por referencia) a los objetos creados por la función Casa
!
- El
prototype
de un constructor es un objeto cualquiera, y puedo modificarlo cuando quiero. El cambio impactará (dinámicamente) en los objetos creados por el constructor (antes y después de la modificación)
A ver si con un ejemplo se entiende (ejecutar y mirar lo consola).
// esta es un funcion que se usará como constructor
function Estadio(nombre, capacidad) {
this.nombre = nombre;
this.capacidad = capacidad;
}
// modificamos directamente el objeto prototype de la funcion constructora , agregando un atributo
// notar que Estadio.prototype no es el prototipo de Estadio
Estadio.prototype.utilidad = 'x';
// creamos dos objetos
// estos objetos tendran como prototipo (oculto) el objeto (unico) Estadio.prototype
var estadio1 = new Estadio('Estadio 1',20);
var estadio2 = new Estadio('Estadio 2',30);
console.log("Nombre de estadio 1:" + estadio1.nombre);
console.log("Nombre de estadio 2:" + estadio2.nombre);
// estas propiedades no estan en cada objeto, pero sí en el prototipo
console.log("Utilidad de estadio 1:" + estadio1.utilidad);
console.log("Utilidad de estadio 2:" + estadio2.utilidad);
// cambiamos el atributo prototype de la funcion Estadio
// (que a su vez está refernciado en el prototipo oculto
//de los dos objetos anteriores
Estadio.prototype.utilidad = 'y';
// vemos que ese cambio "lo ven" los objetos
console.log("Utilidad de estadio 1:" + estadio1.utilidad);
console.log("Utilidad de estadio 2:" + estadio2.utilidad);
// como el atributo no estaba en el objeto (sino en el prototipo), se crea
// en el objeto (la cadena de prototipos se usa para leer, nunca se escribe)
estadio1.utilidad = 'y1';
// ver que ahora los objetos ven diferentes atributos
console.log("Utilidad de estadio 1:" + estadio1.utilidad);
console.log("Utilidad de estadio 2:" + estadio2.utilidad);
// moficiamos nuevamente el prototipo "global"
Estadio.prototype.utilidad = 'z';
// eso solo incide en el segundo objeto, porque el primero lo tiene
console.log("Utilidad de estadio 1:" + estadio1.utilidad);
console.log("Utilidad de estadio 2:" + estadio2.utilidad);
En cuanto a "crear objetos con propiedades comunes". Hay que distinguir: ¿quieres que todas tengan el mismo conjunto de propiedades (pero tal vez con distintos de valores) o quieres que compartan los valores?
Normalmente el constructor (como en mis ejemplos y en el tuyo) definen un conjunto de propiedades con sus valores. Cada objeto creado por ese constructor tendrá esas propiedades con (una copia de) esos valores, que eventualmente después podremos cambiar.
Podríamos lograr algo parecido asignando esas propiedades a un prototipo.
Concretamente, comparar
function Casa() {
this.habitaciones = 2;
}
var c1 = new Casa();
var c2 = new Casa();
con
function Casa() {
}
Casa.prototype.habitaciones = 2;
var c1 = new Casa();
var c2 = new Casa();
El resultado va a ser casi indistinguible, tanto para leer c1.habitaciones
como para escribirlo.
La ventaja del segundo (atributos en el prototipo) es que es más eficiente en manejo de memoria, porque si creo 10 objetos no tengo 10 copias del atributo habitaciones
sino (mientras no lo modifique) uno solo, el que vive en el prototipo.
Esta eficiciencia es MUY importante en algunas aplicaciones de Javascript, en particular para manejar el DOM (arbol de objetos de una página web) y los eventos.
Otra diferencia es que el segundo procedimiento es más flexible porque si, una vez creados los 10 objetos, modifico el prototipo, los 10 objetos (mientras no lo escriban) verán reflejado el atributo. Claro que esto, en algunos casos, puede ser una desventaja.