Como crear objetos en Javascript

Los objetos predefinidos presentan sus propios métodos para crear sus instancias de objetos, como array, strings y otros.

En esta sección vemos las formas en las que se pueden crear objetos propios mediante scripts.

Javascript es un lenguaje que maneja los objetos de forma diferente y la creación de objetos también tiene diferencias con otros lenguajes de programación orientada a objetos

En Javascript tienes varias formas de crear objetos y conctructores:

En cualquier caso recuerda que los objetos que creas viven dentro de un objeto global de Javascript (Object), y heredan propiedades y métodos de este objeto global. Propiedades y métodos que puedes sobreescribir.

En resumen

Javascript es un lenguaje que permite la programación orientada a objetos. Pero lo hace de una forma particular.

La creación de objetos y constructores puede realizarse de varias maneras:

Para objetos individuales se puede usar una expresión directa. Asignar valores a propiedades.

Para objetos y constructores se pueden usar el objeto global Object, el constructor Function o el tipo class.

Para quien conozca los lenguajes orientados a objetos quizás les resulta más cómodo el uso de class, que además deja un código más limpio y fácil de analizar.

Definir objetos con una expresión

Es la forma mas elemental: mediante una expresión con la que se describen las propiedades y métodos del objeto. De esta manera creas un instancia del objeto Object.

Un ejemplo simple: vamos a ver como representar un rectángulo en Javascript. No me refiero a dibujarlo, sino a implementarlo para que Javascript lo maneje. Hacemos una abstracción del rectágulo

let rectangulo = {

    base:0,

    alto:0,

    init: function(x,y){this.base=x; this.alto=y},

    area: function(){return this.base*this.alto}

}

Ya tienes un objeto rectángulo definido. Ahora mismo Javascript ve el rectángulo como un objeto que tiene dos propiedades (base, alto) y dos métodos (init, area).

Fíjate que has definido un nombre ( key ) y le has asignado un valor ( value ). Se les llama pares key:value, y van separados por comas.

También se han creado dos métodos: init para inicializar los valores de las propiedades y area para calcular la superficie de ese rectángulo.

Si quieres crear copias de este objeto no puedes hacerlo directamente, porque los objetos son referencia. Así que cualquiere cambio que hagas en uno de los objetos se refleja en los restantes.

Con la definición de rectángulo del ejemplo anterior ejecuta este script

let sala = rectangulo;

sala.base = 10;

console.log(rectangulo.base); //10

rectangulo.base = 5;

console.log(sala.base);    //5

Y eso no es lo que queremos. Una solución es crear un nuevo objeto desde este.

Ejemplos

Esta forma de crear objetos mediante una expresión es muy sencilla y se usa cuando solo se necesita un objeto concreto, no se van a necesitar objetos similares.

let menu

ficha = {

primero: "Sopa",

segundo : "Filete plancha",

postre: "Fruta fresca",

bebida: "cerveza",

coste: 20,

personas: 10,

total : function(){return this.coste*this.personas}

}

ficha.confirmado = true;

En cualquier momento se pueden aumentar los campos del objeto.

Object create

Para crear otras instancias de un objeto con la misma estructura puedes hacerlo mediante el método del objeto global Object.create(prototype, [def_props])

let rectGrande = Object.create(rectangulo);

rectGrande.init(40, 20);

rectPeq = Object.create(rectangulo);

rectPeq.init(20, 10);

console.log(rectGrande.area());   // 800

console.log(rectPeq.area());     // 200

Básicamente lo que hace este programa es hacer copias del objeto rectangulo. Los objetos creados comparten las propiedades y métodos de rectángulo.

Aquí el objeto rectángulo actúa como un prototype para el objeto creado. Podemos decir que rectPeq y rectGande son objetos derivados de rectangulo o instancias del objeto rectángulo, en terminología de programación orientada a objetos (OOP)

Es decir que rectGrande y rectPeq realmente se crean vacios, pero pueden leer las propiedades alto y base y el método area de rectangulo.

Por tanto los cambios en rectangulo se propagan a sus copias. Si queremos evitarlo es necesario sobreescribir lo que no queremos que se propague. En el ejemplo esa es la razón del método init. Pero se puede hacer a mano.

Este método tiene bastantes más posibilidades que puedes ver en la página dedicada a los métodos estáticos de Object. Aquí vemos como la forma más elemental de crear objetos a patir de un constructor, en este caso un prototype

El esquema de trabajo se ajusta muy bien a la idea de clases - objetos. Se cea un objeto base (una definición del objeto) y a partir de ella se crean instancias o implementaciones para objetos concretos.

Es una forma muy potente, que controla absolutamente todo en la creación de nuevos objetos.

Termino este método con un ejemplo para que veas eso de que los objetos son referencias. Importante para terminar de entender eso de la herencia.

let rectangulo = {

    base:0,

    alto:0,

}

let rect1 = Object.create(rectangulo);

rect1. base = 10; rect1.alto = 20;

let rect2 = rect1;

rect2. base = 5; rect2.alto = 5;

alert("Base rect1: Base: "+rect1.base+ " Alto: " + rect1.alto);

alert("Base rect2: Base: "+rect2.base+ " Alto: " + rect2.alto);

¿Ves que rect1 tiene los valores de rect2? Por que ambos tienen la misma dirección de almacenamiento, hacen referencia al mismo lugar. Escribir o leer de rect2 es lo mismo que hacerlo de rect1, realmente son la misma variable aunque tengan distintos nombres.

Recuerda que dos objetos pueden tener distinto nombre y ser el mismo porque cuando nombramos la variable objeto realmente estamos refiriéndonos al lugar donde está almacenado. Por eso los objetos no se pueden comparar o asignar entre sí como las variables con datos primitivos

Ejemplo

Mascota = {

ficha : 'Animales',

saludar: function(){  console.log(this.voz+" Soy +this.nombre )  },

init : function(n,t,v){

    this.nombre = n;

    this.tipo=t;

    this.voz = v;

    }

}

miPerro = Object.create(Mascota);

miGato = Object.create(Mascota);

miPerro.init('Tom','Perro','guau');

miGato.init('Cati','Gato','miau');

miPerro.saludar();

miGato.saludar();

console.log("¿saludar() iguales? "+(miGato.saludar == miPerro.saludar) );   //true

console.log(miPerro.ficha);

Observa que Mascota es el prototype de miGato y miPerro por lo que estas instancias pueden leer las propiedades y métodos de Mascota.

El método init no es imprescindible, pero facilita la creación de objetos mascota individuales

Las propiedades de Mascota se pueden usar para guardar un valor común a todos los objetos derivados. Estas propiedades son compartidas.

Fíjate que el método init y saludar son el mismo para los objetos derivados miGato y miPerro. Son métodos heredados y compartidos. No son copiados a los objetos derivados.

Funciones como clases

Aclarar un poco el concepto de clase: en la programación orientada a objetos la clase es el definidor de los objetos y de ella derivan los objetos concretos. Javascript puede simular la clase con los constructores, y ese es el uso de Function en esta forma de crear objetos

El método es simple, primero defino la clase con el constructor (como el método init usado en create)

let Rectangulo = function(x,y){

    this.base=x;

    this.alto=y;

    this.area=function(){return this.base * this.alto}

}

De esta forma estás creando una instancia del objeto Function, que a su vez es un constructor y actúa como clase para generar instancias de objetos.

Habitualmente los constructores se nombran coin la inicial en mayúsculas.Es una de esas reglas no escritas de Javascript.

Observa el uso de la palabra clave this. Es un término para referirse al objeto propitario e una función. Asi que this.base se leería como la propiedad base del objeto rectángulo, y this.area sería el método area del objeto rectángulo.

Este código dentro de la función es el constructor de los objetos derivados de Rectangulo y se ejecuta cuando se usa el operador new para crear instancias del objeto.

No se puede acceder directamente a las propiedades y métodos del constructor. Son por así decirlo privadas, solo las lee el operador new

Mira y prueba el ejemplo completo.

let Rectangulo = function(x,y){

    this.base=x;

    this.alto=y;

    this.area=function(){return this.base * this.alto}

}

let rectGrande = new Rectangulo(10,20);

let rectPeq = new Rectangulo(5,4);

console.log(rectGrande.area() );   //200

console.log(rectPeq.area() );    //20

console.log( "¿Métodos area() iguales? "+ (rectGrande.area == rectPeq.area)); 

Como ves al crear las instancias de Rectangulo ya les pones las propiedades directamente.

Pero fíjate en la útima linea del código, el resultado de esa comparación es false. No se están comparando los resultados del método sino las direcciones (los métodos son objetos). Puedes probarlo con dos objetos rectángulo iguales, la comparación no cambia.

let Rectangulo = function(x,y){

    this.base=x;

    this.alto=y;

    this.area=function(){

  return this.base * this.alto}

}

let rectGrande = new Rectangulo(5,4);

let rectPeq = new Rectangulo(5,4);

console.log( "¿areas() iguales? "+ (rectGrande.area == rectPeq.area));   

Ves que los métodos area no son el mismo objeto. Lo que ocurre es que el operador new copia todas las propiedades como base y alto y el método area a los objetos instanciados. No es una herencia verdadera.

Para que realmente haya herencia hay que usar el objeto prototype

let Rectangulo = function(x,y){

    this.base=x;

    this.alto=y;

}

Rectangulo.prototype.area=function(){

  return this.base * this.alto

}

let rectGrande = new Rectangulo(10,20);

let rectPeq = new Rectangulo(5,4);

console.log( "¿areas() iguales? "+ (rectGrande.area == rectPeq.area));   

Ahora los métodos area de ambos objetos son el mismo. Ahora si hay herencia, y el consecuente ahorro de memoria.

Si quieres cambiar el método area puedes hacerlo y el cambio se reflejará en todos los objetos instanciados

Ejemplos

El equivalente al ejemplo de las mascotas usando Function como constructor (clase) sería este.

let Mascota = function(n,t,v){

this.nombre = n;

this.tipo = t;

this.voz = v;

};

Mascota.prototype.saludar = function(){

    console.log(this.voz+" Soy "+this.nombre );

};

Mascota.prototype.ficha= "Animales";

miPerro = new Mascota('Tom','Perro','guau');

miGato = new Mascota('Cati','Gato','miau');

miPerro.saludar();

miGato.saludar();

console.log("¿saludar() iguales? "+(miGato.saludar == miPerro.saludar) );   //true

console.log(miPerro.ficha);

Observa que si las definiciones de saludar y fichas estuvieran dentro de la definición de Mascota, estos se copiarían a las instancias miPerro y miGato. No se usa el método original.

Todo lo que está dentro del cuerpo de la función es inacesible directamente. Ese código es el constructor de las instancias de los objetos derivados. Se ejecuta cuando se invoca el operador new

let Mascota = function(n,t,v){

     this.nombre = n;

     this.tipo = t;

     this.voz = v;

     this.saludar = function(){

  console.log(this.voz+" Soy "+this.nombre );

     };

} ;

miPerro = new Mascota('Tom','Perro','guau');

miGato = new Mascota('Cati','Gato','miau');

miPerro.saludar();

miGato.saludar();

console.log("¿saludar() iguales? "+(miGato.saludar == miPerro.saludar) ); //false

Usando class

El otro método es el que simula las clases tradicionales de programación orientada a objetos. Se trata del elemento class, aunque con esta sentencia realmente se crea un cosntructor Function, pero facilita la herencia y la creación de propiedades estáticas (las accesibles sin instanaciar la clae, desde el propio constructor)

class Rectangulo{

    constructor(x,y){

this.alto=x;

this.ancho=y;

  }

    area(){ return this.alto*this.ancho}

}

rectGrande = new Rectangulo(10,20);

rectPeq = new Rectangulo(8,9);

console.log(rectGrande.area()); // 200

console.log(rectPeq.area()); //72

console.log( "¿areas() iguales? "+ (rectGrande.area == rectPeq.area)); //true

En este ejemplo ves que la clase Rectángulo sobreescribe el método constructor, que es llamado automáticamente cuando se crea el objeto basado en esta clase.

El método constructor es equivalente al código que se escribía dentro del cuerpo en Function.

Todo lo que haya fuera del constructor es el contenido de prototype. Por tanto lo que se escriba en esa parte es heredado por las instancias de objeto dereivados de clase.

Si una propiedad o método se precede on la palabra static se convierte en una propiedad o método estático que solo es accesible por el propio constructor. No se hereda.

class forma{

    constructor(x,y){

this.alto=x;

this.ancho=y;

  }

static area(o){ return o.alto*o.ancho}

}

rectGrande = new Rectangulo(10,20);

rectPeq = new Rectangulo(8,9);

console.log( Rectangulo.area(rectGrande); // 200

console.log( Rectangulo.area(rectPeq); //72

Como puedes ver el método area en este ejemplo es estático y no se usa desde los objetos rectGrande o rectPeq como en los ejemplos anteriores, sino desde el constructor Rectangulo. En este caso area necesita un argumento: el objeto sobre el que se aplica.

El uso de clases permite además extender otras clases. Es decir crea una clase copiando todas sus propiedades y métodos (los propios y los prototype), y luego se le pueden añadir otros.

class MiFecha extends Date{

   semana = ['domingo', 'lunes','martes','miercoles', 'jueves','viernes','sabado'];

   getDiaSem = function(ind){return this.semana[ind]};

}

let dia = new MiFecha();

console.log( dia.getDiaSem( dia.getDay() ));

Ahora tu objeto MiFecha tiene todos las propiedades y métodos de Date y además una función para tener el día de la semana en castellano.

Si un método o propiedad le antepones la palabra clave static se convierte en un método o propiedad estático, que no se hereda y que solo es llamado desde la propia clase.

class Rectangulo{

   constructor(x,y){

      this.alto=x;

      this.ancho=y;

  }

  static info(){return "Argumentos: alto, ancho"};

  area(){ return this.alto*this.ancho};

}

cuadro = new Rectangulo(10,20);

console.log(cuadro.area()); // 200;

console.log(Rectangulo.info());   //"Argumento: alto, ancho"

console.log(cuadro.info()); // Error

El método info es estático, solo puede ser llamado desde la propia clase y no son heredados por las objetos instanciados de esta clase.

Ejemplos

Veamos el ejemplo de la mascota con class

class Mascota {

constructor(n,t,v,){

     this.nombre = n;

     this.tipo = t;

     this.voz = v;

     };

ficha="Animales";

saludar(){

  console.log(this.voz+" Soy "+this.nombre );

   } ;

}

miPerro = new Mascota('Tom','Perro','guau');

miGato = new Mascota('Cati','Gato','miau');

miPerro.saludar();

miGato.saludar();

console.log("miGato.ficha es "+miGato.ficha);

console.log("miPerro.ficha es "+miPerro.ficha);

console.log("¿saludar() iguales? "+(miGato.saludar == miPerro.saludar) ); //true

Con class es extremadamente sencillo de crear constructores de objetos. No es necesario usar específicamente el objeto prototype y además permite crear propiedades estáticas.

Hay una norma de estilo según la cuals las cleses se nombran comenzando por una letra mayúscula: Rectagnulo, Animal, Tienda...