4

¿Cómo se UTILIZA el prototipo de un objeto para ser usado en otros?:

Teniendo este prototipo:

Estadio.prototype {

    Utilidad: 'Fútbol',
    PastoSintetico: true

}

Mis problemas son:

1) ¿A propiedad y método común se refiere a los que todos tendrán, por ende solo debo especificarlos en el prototype?

2) ¿Cómo podría crear 2 objetos con estas propiedades en comunes y tambíen con sus propias propiedades y metodos?

Mi intento fué:

function Estadio1(nombre, capacidad) {

    this.nombre = nombre;
    this.capacidad = capacidad;

}

function Estadio2(nombre, capacidad) {

    this.nombre = nombre;
    this.capacidad = capacidad;

}

3) Al crear estos 3 objetos nuevos, y quiero usar todas sus propiedades incluyendo obviamente las del prototipo ¿es solamente llamar al objeto creado con prototype?, o sea, esto, según mi entendimiento:

Estadio1.prototype.utilidad

o

Estadio1.utilidad

4) ¿Se pueden añadir propiedades a los prototipos?

5) ¿Alguna manera más de crear prototipos?

ElChiniNet
  • 3,215
  • 9
  • 25
Eduardo Sebastian
  • 4,908
  • 7
  • 30
  • 70
  • Como ya deberías saberlo, este tipo de pregutas no son muy bien recibidas en SO, deberías de tratar de editarla. – Luis May 09 '17 at 13:00
  • "Al crear estos 3 objetos nuevos" Dónde estás creando objetos nuevos ? No lo veo. `Estadio1` no es un objeto, es una función. – leonbloy May 09 '17 at 18:46
  • @leonbloy, en `JavaScript` casi todo es un objeto (exceptuando los valores primitivos). Consulta [el segundo párrafo](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Funciones) de la entrada `funciones` en MDN. – ElChiniNet May 09 '17 at 19:12
  • @ElChiniNet Estrictamente hablando, lo que dices es correcto, y lo explico en mi respuesta. Pero estoy hablando en el contexto del que pregunta (donde no está pensando a las funciones como objetos especiales, sino como constructores de objetos). El cree (creo) que ha creado dos objetos Estadio1 y Estadio2 como si fueran dos "instancias" de una especie clase, y no es así, ha creado dos funciones constructoras. – leonbloy May 09 '17 at 19:37
  • @leonbloy, no creo que piense eso, lo que no se ha explicado muy bien la verdad. Esta pregunta estuvo en espera porque era muy amplia y en la edición anterior había pegado un texto de algún tipo de curso donde le pedían crear instancias de un objeto `Planeta` que compartieran las mismas propiedades y tuvieran propiedades particulares. En el historial de edición lo podrás ver. – ElChiniNet May 09 '17 at 19:46

2 Answers2

3

Primero que todo debes saber en qué consiste el prototipo de un objeto en JavaScript. JavaScript es un lenguaje basado en prototipado, los objetos en vez de extender de una clase superior, lo que hacen es compartir las mismas propiedades contenidas en el prototipo del objeto principal que es instanciado.

En resumen:

El prototipo de un objeto es el que contiene todas las propiedades que heredarán las instancias de dicho objeto.

Para ponerte un ejemplo práctico, observa el siguiente código:

function Animal (nombre) {
  this.nombre = nombre;
}

Animal.prototype.camina = function () {
  console.log(this.nombre + " ha echado a andar");
}

var animal1 = new Animal("conejo");
var animal2 = new Animal("caballo");

animal1.camina();
animal2.camina();

Como puedes observar, animal1 y animal2 son instancias del objeto Animal, cada uno tiene una propiedad nombre, la cual es diferente en cada uno de los dos. Sin embargo, ambos comparten el mismo método caminar, que lo que hace es lanzar en la consola un mensaje que contiene la propiedad nombre de cada instancia.

Creo que el anterior snippet responde la mayoría de tus preguntas. Ahora, en cuanto a la pregunta en la que deseas saber si se pueden añadir propiedades a los prototipos, la respuesta es sí, puedes añadir propiedades aunque a mí particularmente no me agrada esa idea, prefiero dejar el prototipo solamente para métodos. Cuando quiero crear propiedades públicas normalmente utilizo Object.defineProperty (si el código necesita ser ejecutado al menos en Internet Explorer 9):

function Animal (nombre) {
  this.nombre = nombre;
}

Object.defineProperty(Animal.prototype, "name", {
  get: function () {
    return this.nombre.toUpperCase();
  }
});

var animal1 = new Animal("conejo");
var animal2 = new Animal("caballo");

console.log( animal1.name );
console.log( animal2.name );

En cuanto a la pregunta de si existe otra manera de crear prototipos, ECMAScript 2015 introduce una nueva forma, usando la declaración class. No es una clase verdadera, al final JavaScript internamente entenderá esto como un objeto con métodos añadidos a su prototipo, pero es una sintaxis mucho más clara y sencilla:

class Animal {

  constructor (nombre) {
    this.nombre = nombre;
  }

  camina () {
    console.log(`${this.nombre} ha echado a andar`);
  }

}

var animal1 = new Animal("conejo");
var animal2 = new Animal("caballo");

animal1.camina();
animal2.camina();

EDICIÓN:

A lo mejor necesitas en un momento determinado crear instancias de un objeto que extienda de otro objeto y como JavaScript no es un lenguaje basado en clases hay que utilizar otras técnicas (básicamente reemplazar el prototipo del objeto con el del objeto del que va a extender y después definir el constructor del nuevo objeto para que no sea el del objeto del que hereda):

//---Animal
function Animal (nombre) {
  this.nombre = nombre;
}

Animal.prototype.getNombre = function () {
  console.log("el nombre del animal es " + this.nombre);
};

//---Mamífero
function Mamifero (nombre) {

  Animal.apply(this, arguments);
  
  this.respira = function () {
    console.log(this.nombre + " ha comenzado a respirar");
  };
  
}

Mamifero.prototype = Object.create(Animal.prototype);
Mamifero.constructor = Mamifero;

//---Reptil
function Reptil (nombre) {

  Animal.apply(this, arguments);
  
  this.repta = function () {
    console.log(this.nombre + " ha comenzado a reptar");
  };
  
}

Reptil.prototype = Object.create(Animal.prototype);
Reptil.constructor = Reptil;


//---Instancias
var conejo = new Mamifero("conejo");
conejo.getNombre();
conejo.respira();

var lagarto = new Reptil("lagarto");
lagarto.getNombre();
lagarto.repta();

El anterior código se vuelve infinitamente más legible y compacto en ECMAScript 2015:

//---Animal
class Animal {

  constructor (nombre) {  
    this.nombre = nombre;  
  }
  
  getNombre () {  
    console.log(`el nombre del animal es ${this.nombre}`);  
  }
  
}

//---Mamífero
class Mamifero extends Animal {

  respira () {
    console.log(`${this.nombre} ha comenzado a respirar`);
  }

}

//---Reptil
class Reptil extends Animal {

  repta () {
    console.log(`${this.nombre} ha comenzado a reptar`);
  }

}

//---Instancias
var conejo = new Mamifero("conejo");
conejo.getNombre();
conejo.respira();

var lagarto = new Reptil("lagarto");
lagarto.getNombre();
lagarto.repta();
ElChiniNet
  • 3,215
  • 9
  • 25
2

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.

leonbloy
  • 2,513
  • 7
  • 18
  • 1
    respuesta completísima, me resolviste mis dudas y hasta mas¡ gracias – Eduardo Sebastian May 10 '17 at 00:57
  • En JavaScript versión 7 hay siete tipos de datos, seis de ellos son primitivos y el séptimo tipo es el objeto, así que siendo estrictos, es incorrecto decir que todo es un objeto. – Rubén Jul 12 '17 at 03:48