2. JavaScript orientado a objetos

2.1. Trabajando con objetos

Todo en JavaScript es un objeto. El ejemplo más básico de un objeto en JavaScript es una cadena.

var cadena = "Yo soy tu padre",
  long = cadena.length;

var minus = cadena.toUpperCase();
console.log(minus);
console.log(cadena);

Podemos observar como la cadena tiene atributos (length) y métodos (toUpperCase()).

Los tipos de datos primitivos son tipos de datos Number, String o Boolean. Realmente no son objetos, y aunque tengan métodos y propiedades, son inmutables.

En cambio, los objetos en JavaScript son colecciones de claves mutables. En JavaScript, los arrays son objetos, las funciones son objetos (Function), las fechas son objetos (Date), las expresiones regulares son objetos (RegExp) y los objetos, objetos son (Object).

Para crear un objeto, podemos usar el tipo de datos Object.

var obj = new Object();
var str = new String();

2.1.1. Propiedades

Un objeto es un contenedor de propiedades (cada propiedad tiene un nombre y un valor), y por tanto, son útiles para coleccionar y organizar datos. Los objetos pueden contener otros objetos, lo que permite estructuras de grafo o árbol.

Para añadir propiedades a un objeto no tenemos más que asignarle un valor utilizando el operador . para indicar que la propiedad forma parte del objeto.

var persona = new Object();
persona.nombre = "Aitor";
persona.apellido1 = "Medrano";

Para averiguar si un objeto contiene un campo, podemos usar el operador in. Finalmente, si queremos eliminar una propiedad de un objeto, hemos de utilizar el operador delete:

console.log('nombre' in persona); // true
delete persona.nombre;
console.log('nombre' in persona); // false

2.1.2. Métodos

Para crear un método, podemos asignar una función anónima a una propiedad, y al formar parte del objeto, la variable this referencia al objeto en cuestión (y no a la variable global como sucede con las funciones declaración/expresión).

persona.getNombreCompleto = function() {
  return this.nombre + " " + this.apellido1;
}

Y para invocar la función invocaremos el método de manera similar a Java:

console.log( persona.getNombreCompleto() );

Realmente el código no se escribe así, es decir, no se crea un Object y posteriormente se le asocian propiedades y métodos, sino que se simplifica mediante objetos literales.

2.2. Objetos literales

Los objetos literales ofrecen una notación para crear nuevos objetos valor. Un objeto literal es un par de llaves que rodean 0 o más parejas de clave:valor separados por comas, donde cada clave se considera como un propiedad/método, de la siguiente manera:

var nadie = {};
var persona = {
  nombre : "Aitor",	(1)
  apellido1 : "Medrano",
  getNombreCompleto : function() {	(2)
    return this.nombre + " " + this.apellido1;
  }
};
1 La propiedad y el valor se separan con dos puntos, y cada una de las propiedades con una coma, de forma similar a JSON.
2 un método es una propiedad de tipo function
Todo objeto literal finaliza con un punto y coma (;)

Para recuperar un campo, además de la notación ., podemos acceder a cualquier propiedad usando la notación de corchetes []:

var nom = persona.nombre;
var ape1 = persona["apellido1"];
var nombreCompleto = persona.getNombreCompleto();
var nombreCompletoCorchete = persona["getNombreCompleto"]();

La notación de corchetes es muy útil para acceder a una propiedades cuya clave está almacenada en una variable que contiene una cadena con el nombre de la misma.

2.2.1. Objetos anidados

Los objetos anidados son muy útiles para organizar la información y representar relaciones contiene con cardinalidades 1 a 1, o 1 a muchos:

var cliente = {
  nombre: "Bruce Wayne",
  email: "bruce@wayne.com",
  direccion: {
    calle: "Mountain Drive",
    num: 1007,
    ciudad: "Gotham"
  }
};

También podemos asignar objetos:

Objetos y propiedades anidadas - http://jsbin.com/qeduze/1/edit?js
var cliente = {};
cliente.nombre = "Bruce Wayne";
cliente.email = "bruce@wayne.com";
cliente.direccion = {};
cliente.direccion.calle = "Mountain Drive";
cliente.direccion.num = 1007;
cliente.direccion.ciudad = "Gotham";

Si accedemos a una propiedad que no existe, obtendremos undefined. Si queremos evitarlo, podemos usar el operador || (or).

var nada = cliente.formaPago; // undefined
var pagoPorDfecto = cliente.formaPago || "Efectivo";

Un fallo muy común es acceder a un campo de una propiedad que no existe. El siguiente fragmento fallará, ya que el objeto direccion no está definido.

var cliente = {};
cliente.direccion.calle = "Mountain Drive";

Si intentamos obtener un valor de una propiedad undefined, se lanzará un excepción TypeError. Para evitar la excepción, podemos usar el operación && (and).

var cliente = {};
cliente.direccion; // undefined
cliente.direccion.calle; //   lanza TypeError
cliente.direccion && cliente.direccion.calle; // undefined

En resumen, un objeto puede contener otros objetos como propiedades. Por ello, podemos ver código del tipo variable.objeto.objeto.objeto.propiedad o variable['objeto']['objeto']['objeto']['propiedad']. Esto se conoce como encadenado de objeto (object chaining).

2.3. Creando un tipo de datos

Si quisiéramos crear dos personas con los mismos atributos que en el ejemplo anterior, tendríamos que repetir mucho código.

2.3.1. Función factoría

Para evitarlo, y que los objetos compartan un interfaz común, podemos crear una función factoría que devuelva el objeto.

function creaPersona(nom, ape1) {
  return {
    nombre : nom,
    apellido1 : ape1,
    getNombreCompleto : function() {
      return this.nombre + " " + this.apellido1;
    }
  };
}

var persona = creaPersona("Aitor", "Medrano"),
  persona2 = creaPersona("Domingo", "Gallardo");

Al tratarse de un lenguaje débilmente tipado, si queremos usar un objeto dentro de un método, es recomendable que comprobemos si existe el método que nos interesa del objeto. Por ejemplo, si queremos añadir un método dentro de una persona, que nos permita saludar a otra, tendríamos lo siguiente:

function creaPersona(nom, ape1) {
  return {
    nombre : nom,
    apellido1 : ape1,
    getNombreCompleto : function() {
      return this.nombre + " " + this.apellido1;
    },
    saluda: function(persona) {
      if (typeof persona.getNombreCompleto !== "undefined") { (1)
        return "Hola " + persona.getNombreCompleto();
      } else {
        return "Hola colega";
      }
    }
  };
}

var persona = creaPersona("Aitor", "Medrano"),
  persona2 = creaPersona("Domingo", "Gallardo");
persona.saluda(persona2); // Hola Domingo Gallardo
persona.saluda({}); // Hola colega
persona.saluda({ getNombreCompleto: "Bruce Wayne" }); // TypeError, la propiedad getNombreCompleto no es una función
1 Comprobamos que el objeto contiene la propiedad que vamos a usar

2.3.2. Función constructor

Otra manera más elegante y eficiente es utilizar es una función constructor haciendo uso de la instrucción new, del mismo modo que creamos una fecha con var fecha = new Date().

Para ello, dentro de la función constructor que nombraremos con la primera letra en mayúscula (convención de código), crearemos las propiedades y los métodos mediante funciones y los asignaremos (ya no usamos la notación JSON) a propiedades de la función, tal que así:

Función constructor - http://jsbin.com/peliq/1/edit?js
var Persona = function(nombre, apellido1) {
  this.nombre = nombre;
  this.apellido1 = apellido1;

  this.getNombreCompleto = function() {
    return this.nombre + " " + this.apellido1;
  };

  this.saluda = function(persona) {
    if (persona instanceof Persona) { (1)
      return "Hola " + persona.getNombreCompleto();
    } else {
      return "Hola colega";
    }
  };
};
1 Al tratarse de un objeto mediante función constructor, ya podemos consultar su tipo con instanceof
instanceof

Mediante el operador instanceof podemos determinar (true o false) si un objeto es una instancia de una determinada función constructor.

Hay que tener en cuenta que devolverá true cada vez que le preguntemos si un objeto es una instancia de Object, ya que todos los objetos heredan del constructor Object().

var heroe = { nombre: "Batman" };
console.log(heroe instanceof Object); // true

En cambio, devolverá false al tratar con tipos primitivos que envuelven objetos, pero true si se crean con el operador new:

console.log("Batman" instanceof String);  // false
console.log(new String("Batman") instanceof String); // true

En resumen, instanceof sólo funciona con objetos complejos e instancias creadas mediante funciones constructor que devuelven objetos

Una vez creada la función, la invocaremos mediante la instrucción new, la cual crea una nueva instancia del objeto:

var persona = new Persona("Aitor", "Medrano"),
	persona2 = new Persona("Domingo", "Gallardo");
persona.saluda(persona2); // Hola Domingo Gallardo
persona.saluda({}); // Hola colega
persona.saluda({ getNombreCompleto: "Bruce Wayne" }); // Hola colega

También podíamos haber comenzado con una sintaxis similar a Java, es decir, en vez de una función expresión, mediante una función declaración. Con lo que sustituiríamos la primera línea por:

Función constructor notación Java - https://jsbin.com/bijejaj/edit?js,console
function Persona(nombre, apellido1) {
  this.nombre = nombre;
  this.apellido1 = apellido1;
  // ....
}
var batman = new Persona("Bruce", "Wayne");
No olvides new

Mucho cuidado con olvidar la palabra clave new, porque de ese modo no estaríamos creando un objeto, sino asignando las propiedades y métodos del objeto al objeto window, lo que no nos daría error, pero no haría lo que esperamos.

Una manera de evitar esta posibilidad, es añadir una comprobación nada más declarar la función:

var Persona = function(nombre, apellido1) {
  if (this === window) {
    return new Persona(nombre, apellido1);
  }

  this.nombre = nombre;
  this.apellido1 = apellido1;
  // continua con el resto del código

Los desarrolladores que vienen (¿venimos?) del mundo de Java preferimos el uso de funciones constructor, aunque realmente al usar una función constructor estamos consiguiendo lo mismo que una función factoría, pero con la variable this siempre referenciando al objeto y no con un comportamiento dinámico como veremos más adelante. Más información: http://ericleads.com/2013/01/javascript-constructor-functions-vs-factory-functions/

En ambos casos, cada vez que creamos un objeto, los métodos vuelven a crearse y ocupan memoria. Así al crear dos personas, los métodos de getNombreCompleto y saluda se crean dos veces cada uno. Para solucionar esto, tenemos que usar la propiedad prototype que veremos más adelante.

2.4. Invocación indirecta

Para poder reutilizar funciones de un objeto entre diferentes objetos, podemos hacer uso del método apply. Este tipo de invocación se conoce como invocación indirecta y permite redefinir el valor de la variable this:

Invocación Indirecta - http://jsbin.com/necohu/1/edit?js

var heroe = {
  nombre: "Superheroe",
  saludar: function() {
    return "Hola " + this.nombre;
  }
};

var batman = { nombre: "Batman" };
var spiderman = { nombre: "Spiderman" };

console.log(heroe.saludar()); // Hola Superheroe
console.log(heroe.saludar.apply(batman)); // Hola Batman
console.log(heroe.saludar.call(spiderman)); // Hola Spiderman

Los métodos apply y call son similares, con la diferencia de que mientras apply además admite un segundo parámetro con un array de argumentos que pasar a la función invocada, call admite un número ilimitado de parámetros que se pasarán a la función.

var heroe = {
  nombre: "Superheroe",
  saludar: function() {
    return "Hola " + this.nombre;
  },
  despedirse: function(enemigo1, enemigo2) {
    var malos = enemigo2 ? (enemigo1 + " y " + enemigo2) : enemigo1;
    return "Adios " + malos + ", firmado:" + this.nombre;
  }
};

var batman = { nombre: "Batman" };
var spiderman = { nombre: "Spiderman" };

console.log(heroe.despedirse()); // Adios undefined, firmado:Superheroe
console.log(heroe.despedirse.apply(batman, ["Joker", "Dos caras"])); // Adios Joker y Dos caras, firmado:Batman
console.log(heroe.despedirse.call(spiderman, "Duende Verde", "Dr Octopus")); // Adios Duende Verde y Dr Octopus, firmado:Spiderman

Una tercera aproximación es usar bind, que funciona de manera similar a las anteriores, pero en vez de realizar la llamada a la función, devuelve una función con el contexto modificado.

var funcionConBatman = heroe.despedirse.bind(batman);
console.log(funcionConBatman("Pingüino")); // Adios Pingüino, firmado:Batman
console.log(funcionConBatman("Mr Frio")); // Adios Mr Frio, firmado:Batman

Antes de enamorarse de bind, conviene destacar que forma parte de ECMAScript 5, por lo que los navegadores antiguos no lo soportan. Se emplea sobre todo cuando usamos un callback y en vez de guardar una referencia a this en una variable auxiliar (normalmente nombrada como that), hacemos uso de bind para pasarle this al callback.

Por ejemplo, cuando se trabaja con AJAX suele suceder esto:

var that = this;
function callback(datos){
  that.procesar(datos);
}
ajax(callback);

Ahora con bind nos quedaría asi:

function callback(datos){
  this.procesar(datos);
}
ajax(callback.bind(this));

2.5. Descriptores de propiedades

Al definir las propiedades mediante un objeto literal, estas se pueden tanto leer como escribir, ya sea mediante la notación . o [].

Si queremos que nuestro objeto contenga propiedades privadas, sólo hay que declararlas como variables dentro del objeto:

Ejemplo propiedad privada
function Persona(nombre, apellido1) {
  var tipo = "Heroe";
  this.nombre = nombre;
  this.apellido1 = apellido1;
}

var batman = new Persona("Bruce", "Wayne");
console.log(batman.nombre); // Bruce
console.log(batman.tipo); // undefined

Si lo que necesitamos es restringir el estado de las propiedades, a partir de ECMAScript 5, podemos usar:

  • un descriptor de datos para las propiedades que tienen un valor, el cual puede ser de sólo lectura, mediante Object.defineProperties

  • o haciendo uso de los descriptores de acceso que definen dos funciones, para los métodos get y set.

2.5.1. Definiendo propiedades

Vamos a recuperar el ejemplo de la función factoría que creaba una persona:

function creaPersona(nom, ape1) {
  return {
    nombre : nom,
    apellido1 : ape1,
    getNombreCompleto : function() {
      return this.nombre + " " + this.apellido1;
    }
  };
}

Para definir las propiedades, haremos uso de Object.defineProperty() rellenando las propiedades value con la variable de la cual tomará el valor, y writable con un booleano que indica si se puede modificar (si no la rellenamos, por defecto se considera que el atributo es de sólo lectura, es decir, false)

function creaPersona(nom, ape1) {
  var persona = {};

  Object.defineProperty(persona, "nombre", {
    value: nom,
    writable: true
  });

  Object.defineProperty(persona, "apellido1", {
    value: ape1,
    writable: false
  });

  return persona;
}

De este modo, podemos crear personas, en las cuales podremos modificar el nombre pero no el apellido:

var batman = creaPersona("Bruce", "Wayne");
console.log(batman.nombre, batman.apellido1);
batman.nombre = "Bruno";
batman.apellido1 = "Díaz";  // No se lanza ningún error, pero no modifica
console.log(batman.nombre, batman.apellido1);

En vez de tener que crear una instrucción Object.defineProperty() por propiedad, podemos agrupar y usar Object.defineProperties() y simplificar el código:

Descriptor de propiedades - http://jsbin.com/loqofe/1/edit?js
function creaPersona(nom, ape1) {
  var persona = {};

  Object.defineProperties(persona, {
    nombre: {
      value: nom,
      writable: true
    },
    apellido1: {
      value: ape1,
      writable: false
    }
  });

  return persona;
}

Si en vez de utilizar una función factoría, queremos hacerlo mediante una función constructor, el funcionamiento es el mismo, sólo que el objeto que le pasaremos a Object.defineProperty() será this:

Descriptor de propiedades con función constructor - https://jsbin.com/mipohe/edit?js,console
function Persona(nom, ape1) {
  this.nombre = nom;

  Object.defineProperties(this, {
    apellido1: {
      value: ape1,
      writable: false
    }
  });
}

Finalmente, si en algún momento queremos consultar un descriptor de una propiedad haremos uso de Object.getOwnPropertyDescriptor(objeto, propiedad):

var batman = creaPersona("Bruce", "Wayne");
console.log(Object.getOwnPropertyDescriptor(batman, "nombre"));
//  [object Object] {
//    configurable: false,
//    enumerable: false,
//    value: "Bruce",
//    writable: true
//  }

2.5.2. Get y Set

Los descriptores de acceso sustituyen a los métodos que modifican las propiedades. Para ello, vamos a crear una propiedad nombreCompleto con sus respectivos métodos de acceso y modificación:

function creaPersona(nom, ape1) {
  var persona = {};

  Object.defineProperties(persona, {
    nombre: {
      value: nom,
      writable: true
    },
    apellido1: {
      value: ape1,
      writable: false
    },
    nombreCompleto: {
      get: function() { (1)
        return this.nombre + " " + this.apellido1;
      },
      set: function(valor) {  (2)
        this.nombre = valor;
        this.apellido1 = valor;
      }
    }
  });

  return persona;
}
1 Método de acceso
2 Método de modificación. Destacar que como hemos definido la propiedad 'apellido1' como de sólo lectura, no va a cambiar su valor

De este modo podemos obtener la propiedad del descriptor de acceso como propiedad en vez de como método:

var batman = creaPersona("Bruce", "Wayne");
console.log(batman.nombreCompleto); // Bruce Wayne
batman.nombreCompleto = "Bruno Díaz";
console.log(batman.nombreCompleto); // Bruno Díaz Wayne
Propiedades del objeto Persona
Figure 1. Objeto con propiedades
Una propiedad no puede contener al mismo tiempo el atributo value y get o set, es decir no puede ser descriptor de datos y de acceso al mismo tiempo.

2.5.3. Iterando sobre las propiedades

Si necesitamos acceder a todas las propiedades que contiene un objeto, podemos recorrerlas como si fueran una enumeración mediante un bucle for in:

for (var prop in batman) {
  console.log(batman[prop]);
}

O hacer uso del método Object.keys(objeto), el cual nos devuelve las propiedades del objeto en forma de array:

var propiedades = Object.keys(batman);

Por defecto las propiedades definidas mediante descriptores no se visualizan al recorrerlas. Para poder visualizarlas, tenemos que configurar la propiedad enumerable para cada propiedad que queramos obtener:

function creaPersona(nom, ape1) {
  var persona = {};

  Object.defineProperties(persona, {
    nombre: {
      value: nom,
      enumerable: true  (1)
    },
    apellido1: {
      value: ape1,
      enumerable: true
    },
    nombreCompleto: {
      get: function() {
        return this.nombre + " " + this.apellido1;
      },
      enumerable: false (2)
    }
  });

  return persona;
}

var batman = creaPersona("Bruce", "Wayne");
console.log(Object.keys(batman)); (3)
1 Marcamos la propiedad como enumerable
2 La marcamos para que no aparezca
3 Obtenemos un array con ["nombre", "apellido1"]

2.5.4. Modificando una propiedad

Si por defecto intentamos redefinir una propiedad que ya existe, obtendremos un error. Para poder hacer esto, necesitamos configurar la propiedad configurable a true, ya que por defecto, si no la configuramos es false.

Así pues, si ahora queremos que la propiedad de nombreCompleto devuelva el apellido y luego el nombre separado por una coma, necesitaríamos lo siguiente:

function creaPersona(nom, ape1) {
  var persona = {};

  Object.defineProperties(persona, {
    nombre: {
      value: nom,
    },
    apellido1: {
      value: ape1,
    },
    nombreCompleto: {
      get: function() {
        return this.nombre + " " + this.apellido1;
      },
      configurable: true  (1)
    }
  });

  return persona;
}

Object.defineProperty(persona, "nombreCompleto", {  (2)
  get: function() {
    return this.apellido1 + ", " + this.nombre;
  }
});
1 Permitimos modificar la propiedad una vez definido el objeto
2 Redefinimos la propiedad

2.6. Prototipos

Los prototipos son una forma adecuada de definir tipos de objetos que permiten definir propiedades y funcionalidades que se aplicarán a todas las instancias del objeto. Es decir, es un objeto que se usa como fuente secundaria de las propiedades. Así pues, cuando un objeto recibe un petición de una propiedad que no contiene, buscará la propiedad en su prototipo. Si no lo encuentra, en el prototipo del prototipo, y así sucesivamente.

A nivel de código, todos los objetos contienen una propiedad prototype que inicialmente referencia a un objeto vacío. Esta propiedad no sirve de mucho hasta que la función se usa como un constructor.

Por defecto todos los objetos tienen como prototipo raíz Object.prototype, el cual ofrece algunos métodos que comparten todos los métodos, como toString. Si queremos averiguar el prototipo de un objeto podemos usar la función Object.getPrototypeOf(objeto).

console.log(Object.getPrototypeOf({}) == Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null

2.6.1. Constructores y prototype

Al llamar a una función mediante la instrucción new provoca que se invoque como un constructor. El constructor asocia la variable this al objeto creado, y a menos que se indique, la llamada devolverá este objeto.

Este objeto se conoce como una instancia de su constructor. Todos los constructores (de hecho todas las funciones) automáticamente contienen la propiedad prototype que por defecto referencia a un objeto vacío que deriva de Object.prototype.

Cada instancia creada con este constructor tendrá este objeto como su prototipo. Con lo que para añadir nuevos métodos al constructor, hemos de añadirlos como propiedades del prototipo.

var Persona = function(nombre, apellido1) {
  this.nombre = nombre;
  this.apellido1 = apellido1;
}

Persona.prototype.getNombreCompleto = function() {
  return this.nombre + " " + this.apellido1;
};

Persona.prototype.saluda = function(persona) {
  if (persona instanceof Persona) {
    return "Hola " + persona.getNombreCompleto();
  } else {
    return "Hola colega";
  }
};

var persona = new Persona("Aitor", "Medrano"),
  persona2 = new Persona("Domingo", "Gallardo");
persona.saluda(persona2); // Hola Domingo Gallardo
persona.saluda({}); // Hola colega
persona.saluda({ getNombreCompleto: "Bruce Wayne" }); // Hola colega

Una vez definido el prototipo de un objeto, las propiedades del prototipo se convierten en propiedades de los objetos instanciados. Su propósito es similar al uso de clases dentro de un lenguaje clásico orientado a objeto. De hecho, el uso de prototipos en JavaScript se plantea para poder compartir código de manera similar al paradigma orientado a objetos.

Ya hemos comentado que todo lo que colocamos en la propiedad prototype se comparte entre todas las instancias del objeto, por lo que las funciones que coloquemos dentro compartirán una única instancia entre todas ellas.

Si queremos compartir el prototipo entre diferentes objetos, podemos usar Object.create para crear un objeto con un prototipo específico, aunque es mejor usar una función constructor.

2.6.2. prototype y __proto__

Supongamos el siguiente objeto vacío:

var objeto = {};
console.dir(objeto);

Si inspeccionamos la consola, podemos observar la propiedad __proto__ con todas sus propiedades:

Propiedades de un objeto vacío
Figure 2. Propiedades de un Objeto

Así pues tenemos que todo objeto contiene una propiedad __proto__ que incluye todas las propiedades que hereda nuestro objeto. Es algo así como el padre del objeto. Para recuperar este prototipo podemos usar el método Object.getPrototypeOf() o directamente navegar por la propiedad __proto__, es decir, son intercambiables.

Es importante destacar la diferencia entre el modo que un prototipo se asocia con un constructor (a través de la propiedad prototype) y el modo en que los objetos tienen un prototipo (el cual se puede obtener mediante Object.getPrototypeOf). El prototipo real de un constructor es Function.prototype ya que todos los constructores son funciones. Su propiedad prototype será el prototipo de las instancias creadas mediante su constructor, pero no su propio prototipo.

Los prototipos en JavaScript son especiales por lo siguiente: cuando le pedimos a JavaScript que queremos invocar el método push de un objeto, o leer la propiedad x de otro objeto, el motor primero buscará dentro de las propiedades del propio objeto. Si el motor JS no encuentra lo que nosotros queremos, seguirá la referencia __proto__ y buscará el miembro en el prototipo del objeto.

Veamos un ejemplo mediante código:

__proto__ vs prototype - http://jsbin.com/sicivi/1/edit?js
function Heroe(){
  this.malvado = false;
  this.getTipo = function() {
    return this.malvado ? "Malo" : "Bueno";
  };
}

Heroe.prototype.atacar = function() {
  return this.malvado ? "Ataque con Joker" : "Ataque con Batman";
}

var robin = new Heroe();
console.log(robin.getTipo());   // Bueno
console.log(robin.atacar());     // Ataque con Batman

var lexLuthor = new Heroe();
lexLuthor.malvado = true;
console.log(lexLuthor.getTipo());   // Malo
console.log(lexLuthor.atacar());     // Ataque con Joker

var policia = Object.create(robin);
console.log(policia.getTipo());     // Bueno
console.log(policia.__proto__.atacar());    // Ataque con Batman

Ya hemos visto que cada objeto en JavaScript tiene una propiedad prototype. No hay que confundir esta propiedad prototype con la propiedad __proto__, ya que ni tienen el mismo propósito ni apuntan al mismo objeto:

  • Array.__proto__ nos da el prototipo de Array, es decir, el objeto del que hereda la función Array.

  • Array.prototype, en cambio, es el objeto prototipo de todos los arrays, que contiene los métodos que heredarán todos los arrays.

Para finalizar, relacionado con estos conceptos tenemos la instrucción new en JavaScript, la cual realiza tres pasos:

  1. Primero crea un objeto vacío.

  2. A continuación, asigna la propiedad __proto__ del nuevo objeto a la propiedad prototype de la función invocada

  3. Finalmente, el operador invoca la función y pasa el nuevo objeto como la referencia this.

Un artículo muy gráfico que explica la diferencia entre estas dos propiedades es Prototypes and Inheritance in JavaScript de Scott Allen : http://msdn.microsoft.com/en-us/magazine/ff852808.aspx

2.7. Herencia

JavaScript es un lenguaje de herencia prototipada, lo que significa que un objeto puede heredar directamente propiedades de otro objeto a partir de su prototipo, sin necesidad de crear clases.

Ya hemos visto que mediante la propiedad prototype podemos asociar atributos y métodos al prototipo de nuestras funciones constructor.

Retomemos el ejemplo del objeto Persona mediante la función constructor:

var Persona = function(nombre, apellido1) {
  this.nombre = nombre;
  this.apellido1 = apellido1;

  this.nombreCompleto = function() {
    return this.nombre + " " + this.apellido1;
  };

  this.saluda = function(persona) {
    if (persona instanceof Persona) {
      return "Hola " + persona.getNombreCompleto();
    } else {
      return "Hola colega";
    }
  };
};

Para mejorar el uso de la memoria y reducir la duplicidad de los métodos, hemos de llevar los métodos al prototipo. Para ello, crearemos descriptores de acceso para las propiedades y llevaremos los métodos al prototipo de la función constructor:

var Persona = function(nombre, apellido1) {
  this.nombre = nombre;
  this.apellido1 = apellido1;
};

Object.defineProperties(Persona.prototype, {  (1)
  nombreCompleto: {
    get: function() { (2)
      return this.nombre + " " + this.apellido1;
    },
    enumerable: true
  }
});

Persona.prototype.saluda = function(persona) {  (3)
  if (persona instanceof Persona) {
    return "Hola " + persona.nombreCompleto;
  } else {
    return "Hola colega";
  }
};

var batman = new Persona("Bruce", "Wayne");
console.log(batman.nombreCompleto); // Bruce Wayne
console.log(batman.saluda()); // Hola colega
var superman = new Persona("Clark", "Kent");
console.log(batman.saluda(superman)); // Hola Clark Kent
1 Fijamos la definición de propiedades en el prototipo de la función constructor
2 Añadimos una propiedad de acceso por cada propiedad
3 Añadimos los métodos al prototipo

Si queremos incluir los métodos como dentro de los descriptores de propiedades, podemos añadiros como valores:

Object.defineProperties(Persona.prototype, {
  nombreCompleto: {
    get: function() {
      return this.nombre + " " + this.apellido1;
    },
    enumerable: true
  },
  saluda: {
    value: function(persona) {  (1)
      if (persona instanceof Persona) {
        return "Hola " + persona.nombreCompleto;
      } else {
        return "Hola colega";
      }
    },
    enumerable: true
  }
});
1 El método saluda ahora es una propiedad cuyo valor es una función

Para crear un objeto que utilice el prototipo de otro hemos de hacer uso del método Object.create(objetoPrototipo). De este modo, el objetoPrototipo se convierte en el prototipo del objeto devuelto, y así podemos acceder al objeto padre mediante Object.getPrototypeOf(objetoPrototipo) o la propiedad __proto__ (deprecated):

var Empleado = Object.create(Persona);
console.log(Empleado.hasOwnProperty('nombreCompleto'));  // false
console.log(Empleado.__proto__ === Persona);  // true
console.log(Object.getPrototypeOf(Empleado) === Persona);  // true

Si queremos realizar herencia entre objetos, el proceso se realiza en dos pasos:

  1. Heredar el constructor

  2. Heredar el prototipo

2.7.1. Herencia de constructor

Si usamos funciones constructor, podemos realizar herencia de constructor para que el hijo comparta las mismas propiedades que el padre. Para ello, el hijo debe realizar una llamada al padre y definir sus propios atributos.

Por ejemplo, supongamos que queremos crear un objeto Empleado que se base en Persona, pero añadiendo el campo cargo con el puesto laboral del empleado:

var Empleado = function(nombre, apellido1, cargo) {
  Persona.call(this, nombre, apellido1);  (1)
  this.cargo = cargo;
};
1 Llamamos al constructor del padre mediante call para que this tome el valor del hijo.

2.7.2. Herencia de prototipo

Una vez heredado el constructor, necesitamos heredar el prototipo para compartir los métodos y si fuese el caso, sobrescribirlos.

Para ello, mediante Object.create(prototipo, propiedades) vamos a definir los métodos del hijo y si quisiéramos sobrescribir los métodos que queramos del padre:

Empleado.prototype = Object.create(Persona.prototype, { (1)
  saluda: {   // sobreescribimos los métodos que queremos
    value: function(persona) {
      if (persona instanceof Persona) {
        return Persona.prototype.saluda.call(this) + " (desde un empleado)";  (2)
      } else {
        return "Hola trabajador";
      }
    },
    writable: false,  (3)
    enumerable: true
  },
  nombreCompleto: {
    get: function() { (4)
      var desc = Object.getOwnPropertyDescriptor(Persona.prototype, "nombreCompleto");  (5)
      return desc.get.call(this) + ", " + this.cargo; (6)
    },
    enumerable: true
  }
});
1 Redefinimos el prototipo de Empleado con el de Persona
2 Dentro del método que sobreescribimos, podemos realizar una llamada al mismo método pero del padre, haciendo uso de call para redefinir el objeto this
3 Podemos marcar el método como no modificable para evitar que se pueda sobreescribir
4 Si queremos, también podemos sobreescribir los descritores de propiedades
5 Para acceder a una propiedad del padre hemos de hacerlo mediante Object.getOwnPropertyDescriptor
6 Igual que en punto 2, accedemos al descriptor pero asociándole el objeto this del hijo al padre.
Un artículo interesante sobre la OO y la herencia en JavaScript es OOP in JavaScript: What you NEED to know: http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/

2.8. this y el patrón invocación

Una de las cosas que diferencia JavaScript de otros lenguajes de programación es que la variable this toma diferentes valores dependiendo de cómo se invoque la función o fragmento donde se encuentra.

A la hora de invocar una función lo podemos hacer de cuatro maneras, las cuales se conocen como el patrón invocación:

  • El patrón de invocación como método

  • El patrón de invocación como función

  • El patrón de invocación como constructor

  • El patrón de invocación con apply y call

Este patrón define como se inicializa this en cada caso.

2.8.1. Invocación como método

Se conoce como método a aquella función que se almacena como una propiedad de un objeto. En este caso this se inicializa con el objeto al que pertenece la función.

var obj = {
	valor : 0,
	incrementar: function(inc){
		this.valor += inc;
	}
};

obj.incrementar(3);
console.log(obj.valor); // 3

Al asociarse el valor de this en tiempo de invocación (y no de compilación), fomenta que el código sea altamente reutilizable.

Los métodos que hacen uso de this para obtener el contexto del objeto se conocen como métodos públicos.

2.8.2. Invocación como Función

Cuando una función no es una propiedad de un objeto, se invoca como función, y this se inicializa con el objeto global (al trabajar con un navegador, el objeto window).

function suma(a,b) {
	console.log(a+b);
	console.log(this);
}
suma(3,5);

Y por la consola aparece tanto el resultado como todo el objeto window:

8
Window {top: Window, window: Window, location: Location, ... }

Esto puede ser un problema, ya que cuando llamamos a una función dentro de otra, this sigue referenciando al objeto global y si queremos acceder al this de la función padre tenemos que almacenarlo previamente en una variable:

var obj = {
  valor: 0,
  incrementar: function(inc) { (1)
    var that = this;

    function otraFuncion(unValor) {  (2)
      that.valor += unValor;
    }

    otraFuncion(inc);
  }
};

obj.incrementar(3);
console.log(obj.valor); // 3
1 Se invoca como método y this referencia al propio objeto
2 Se invoca como función y this referencia al objeto global

Y si hacemos uso de call podemos conseguir lo mismo pero sin necesidad de almacenar this en una variable auxiliar:

var objBind = {
  valor: 0,
  incrementar: function(inc) {
    function otraFuncion(unValor) {
      this.valor += unValor;
    }
    otraFuncion.call(this, inc);  (1)
  }
}
1 Al invocar a una función, le indicamos que toma la referencia this del objeto en vez del global

2.8.3. Invocación como constructor

Ya hemos visto que JavaScript ofrece una sintaxis similar a la creación de objetos en Java. Dicho esto, cuando invocamos una función mediante new se creará un objeto con una referencia al valor de la propiedad prototype de la función (también llamado constructor) y this tendrá una referencia a este nuevo objeto.

var Persona = function() { // constructor
  this.nombre = 'Aitor';
  this.apellido1 = "Medrano";
}

Persona.prototype.getNombreCompleto = function(){
  return this.nombre + " " + this.apellido1;
}

var p = new Persona();
console.log(p.getNombreCompleto()); // Aitor Medrano

2.8.4. Invocación con apply

Como JavaScript es un lenguaje orientado a objetos funcional, las funciones pueden contener métodos.

El método apply nos permite, además de construir un array de argumentos que usaremos al invocar una función, elegir el valor que tendrá this, lo cual permite reescribir el valor de this en tiempo de ejecución.

Para ello, apply recibe 2 parámetros, el primero es el valor para this y el segundo es un array de parámetros.

Usando el ejemplo anterior de prototipado, vamos a cambiar el this utilizando apply.

var Persona = function() { // constructor
  this.nombre = "Aitor";
  this.apellido1 = "Medrano";
}

Persona.prototype.getNombreCompleto = function(){
  return this.nombre + " " + this.apellido1;
}

var otraPersona = {
  nombre: "Rubén",
  apellido1: "Inoto"
}

var p = new Persona();
console.log(p.getNombreCompleto()); // Aitor Medrano
console.log(p.getNombreCompleto().apply(otraPersona)); // Rubén Inoto

Así pues, el método apply realiza una llamada a una función pasándole tanto el objeto que va a tomar el papel de this como un array con los parámetros que va a utilizar la función.

Autoevaluación - this

¿Cual es el resultado del siguiente fragmento de código? [1]

var nombre = 'Bruce Wayne';
var obj = {
  nombre: 'Bruno Díaz',
  prop: {
    nombre: 'Aitor Medrano',
    getNombre: function() {
      return this.nombre;
    }
  }
};
console.log(obj.prop.getNombre());
var test = obj.prop.getNombre;
console.log(test());

2.9. Arrays

Se trata de un tipo predefinido que, a diferencia de otros lenguajes, es un objeto. Del mismo modo que los tipos básicos, lo podemos crear de la siguiente manera:

var cosas = new Array();
var tresTipos = new Array(11, "hola", true);
var longitud = tresTipos.length;	// 3
var once = tresTipos[0];

Podemos observar que en JavaScript los arrays pueden contener tipos diferentes, que el primer elemento es el 0 y que podemos obtener su longitud mediante la propiedad length.

Igual que antes, aunque se pueden crear los arrays de este modo, realmente se crean e inicializan con la notación de corchetes de JSON:

var tresTipos = [11, "hola", true];
var once = tresTipos[0];

Podemos añadir elementos sobre la marcha y en la posiciones que queramos (aunque se recomienda añadir los elementos en posiciones secuenciales).

Accediendo a arrays - http://jsbin.com/zegepa/2/edit?js
tresTipos[3] = 15;
tresTipos[tresTipos.length] = "Bruce";
var longitud2 = tresTipos.length; // 5
tresTipos[8] = "Wayne";
var longitud3 = tresTipos.length; // 9
var nada = tresTipos[7]; // undefined

Cabe destacar que si accedemos a un elemento que no contiene ningún dato obtendremos undefined.

Autoevaluación

¿Sabes cual es el contenido del array tiposTres tras ejecutar todas las instrucciones? [2]

Por lo tanto, si añadimos elementos en posiciones mayores al tamaño del array, éste crecerá con valores undefined hasta el elemento que añadamos.

Si en algún momento quisiéramos eliminar un elemento del array, hemos de usar el operador delete sobre el elemento en cuestión.

delete tresTipos[1];

El problema es que delete deja el hueco, y por tanto, la longitud del array no se ve reducida, asignándole undefined al elemento en cuestión.

Array de palabras

Al trabajar con una cadena de texto, una operación que se utiliza mucho es, dividirla en trozos a partir de un separador. Al utilizar el método String.split(separador), éste devolverá un array de cadenas con cada uno de los trozos.

var frase = "Yo soy Batman";
var arrayPalabras = frase.split(" ");
var yo = arrayPalabras[0];
var soy = arrayPalabras[1];
var Batman = arrayPalabras[2];

2.9.1. Manipulación

Los arrays soportan los siguientes métodos para trabajar con elementos individuales:

Métodos Propósito

pop()

Extrae y devuelve el último elemento del array

push(elemento)

Añade el elemento en la última posición

shift()

Extrae y devuelve el primer elemento del array

unshift(elemento)

Añade el elemento en la primera posición

Modificando un array - http://jsbin.com/kefexa/6/edit?js
var notas = ['Suspenso', 'Aprobado', 'Bien', 'Notable', 'Sobresaliente'];

notas.push('Matrícula de Honor');
var matricula = notas.pop(); // "Matrícula de Honor"
var suspenso = notas.shift(); // "Suspenso"
notas.unshift('Suspendido');

console.log(notas);

Además, podemos usar los siguiente métodos que modifican los arrays en su conjunto:

Métodos Propósito

concat(array2[,…​, arrayN])

Une dos o más arrays

join(separador)

Concatena las partes de un array en una cadena, indicándole como parámetro el separador a utilizar

reverse()

Invierte el orden de los elementos del array, mutando el array

sort()

Ordena los elementos del array alfabéticamente, mutando el array

sort(fcomparacion)

Ordena los elementos del array mediante la función fcomparacion

slice(inicio, fin)

Devuelve un nuevo array con una copia con los elementos comprendidos entre inicio y fin (con índice 0, y sin incluir fin).

splice(índice, cantidad, elem1[, …​, elemN])

Modifica el contenido del array, añadiendo nuevos elementos mientras elimina los antiguos seleccionando a partir de índice la cantidad de elementos indicados. Si cantidad es 0, sólo inserta los nuevos elementos.

Hay que tener en cuenta que los métodos mutables modifican el array sobre el que se realiza la operación:

Modificando un array - http://jsbin.com/kefexa/6/edit?js
var notas = ['Suspenso', 'Aprobado', 'Bien', 'Notable', 'Sobresaliente'];

notas.reverse();
console.log(notas);  // ["Sobresaliente", "Notable", "Bien", "Aprobado", "Suspenso"]

notas.sort();
console.log(notas);  // ["Aprobado", "Bien", "Notable", "Sobresaliente", "Suspenso"]

notas.splice(0, 4, "Apto"); (1)
console.log(notas); // ["Apto", "Suspenso"]
1 A partir de la posición 0, borra 4 elementos y añade "Apto".
Autoevaluación

¿Cual es el valor de la variables iguales? [3]

Arrays Autoevaluación - http://jsbin.com/gawaju/1/edit?js
var nombreChicos = ["Juan", "Antonio"];
nombreChicos.push("Pedro");
var nombreChicas = ["Ana", "Laura"];
var nombres = nombreChicos.concat(nombreChicas);
var separadoConGuiones = nombres.join("-");
nombres.reverse();
var alfa = nombres[3];
nombres.sort();
var iguales = (alfa == nombres[2]);

Una operación muy usual es querer ordenar un array de objetos por un determinado campo del objeto. Supongamos que tenemos los siguientes datos:

Ordenando Array de Objetos - http://jsbin.com/toluxe/edit?js
var personas = [
  {nombre:"Aitor", apellido1:"Medrano"},
  {nombre:"Domingo", apellido1:"Gallardo"},
  {nombre:"Alejandro", apellido1:"Such"}
];

personas.sort(function(a,b) {
  if (a.nombre < b.nombre)
    return -1;
  if (a.nombre > b.nombre)
    return 1;
  return 0;
});

Por lo tanto, la función de comparación siempre tendrá la siguiente forma:

function compare(a, b) {
  if (a es menor que b según criterio de ordenamiento) {
    return -1;
  }
  if (a es mayor que b según criterio de ordenamiento) {
    return 1;
  }
  // a debe ser igual b
  return 0;
}

Finalmente, vamos a estudiar los métodos slice y splice que son menos comunes.

var frutas = ["naranja", "pera", "manzana", "uva", "fresa", "naranja"];
var arrayUvaFresa = frutas.slice(3, 5); (1)
var uvaFresa = frutas.splice(3, 2, "piña"); (2)
1 Crea un nuevo array con los elementos comprendidos entre el tercero y el quinto, quedando ["uva", "fresa"]
2 Tras borrar dos elementos a partir de la posición tres, añade piña. En uvaFresa se almacena un array con los elementos eliminados (["uva", "fresa"]), mientras que frutas se queda con ["naranja", "pera", "manzana", "piña", "naranja"].

Si lo que queremos es buscar un determinado elemento dentro de un array:

Método Propósito

indexOf(elem[, inicio])

Devuelve la primera posición (0..n-1) del elemento comenzando desde el principio o desde inicio

lastIndexOf(elem[, inicio])

Igual que indexOf pero buscando desde el final hasta el principio

En ambos casos, si no encuentra el elemento, devuelve -1.

Autoevaluación

¿Cual es el valor de la variable ultima?

var frutas = ["naranja", "pera", "manzana", "uva", "fresa", "naranja"];
var primera = frutas.indexOf("naranja");
var ultima = frutas.lastIndexOf("naranja");

2.9.2. Iteración

Los siguiente métodos aceptan una función callback como primer argumento e invocan dicha función para cada elemento del array. La función que le pasamos a los métodos reciben tres parámetros:

  1. El valor del elemento del array

  2. El índice del elemento

  3. El propio array

La mayoría de las veces sólo necesitamos utilizar el valor.

Los métodos que podemos utilizar son:

Método Propósito

forEach(función)

Ejecuta la función para cada elemento del array

map(función)

Ejecuta la función para cada elemento del array, y el nuevo valor se inserta como un elemento del nuevo array que devuelve.

every(función)

Verdadero si la función se cumple para todos los valores. Falso en caso contrario (Similar a una conjunción → Y)

some(función)

Verdadero si la función se cumple para al menos un valor. Falso si no se cumple para ninguno de los elementos (Similar a un disyunción → O)

filter(función)

Devuelve un nuevo array con los elementos que cumplen la función

reduce(función)

Ejecuta la función para un acumulador y cada valor del array (de inicio a fin) se reduce a un único valor

A continuación vamos a estudiar algunos de estos métodos mediante ejemplos.

Si queremos pasar a mayúsculas todos los elementos del array, podemos usar la función map(), la cual se ejecuta para cada elemento del array:

var heroes = ["Batman", "Superman", "Ironman", "Thor"];

function mayus(valor, indice, array) {
  return valor.toUpperCase();
}

var heroesMayus = heroes.map(mayus);
console.log(heroesMayus); // ["BATMAN", "SUPERMAN", "IRONMAN", "THOR"]

O si queremos mostrar todos los elementos del array, podemos hacer uso del método forEach:

var heroes = ["Batman", "Superman", "Ironman", "Thor"];
heroes.forEach(function(valor, indice) {
  console.log("[", indice, "]=", valor);
});

O si tenemos un array con números también podemos sumarlos mediante forEach:

var numeros = [1, 3, 5, 7, 9];
var suma = 0;
numeros.forEach(function(valor) {
  suma += valor
});

Si queremos comprobar si todos los elementos de un array son cadenas, podemos utilizar el método every(). Para ello, crearemos una función esCadena:

function esCadena(valor, indice, array) {
  return typeof valor === "string";
}

console.log(frutas.every(esCadena)); // true

Si tenemos un array con datos mezclados con textos y números, podemos quedarnos con los elementos que son cadenas mediante la función filter().

var mezcladillo = [1, "dos", 3, "cuatro", 5, "seis"];

console.log(mezcladillo.filter(esCadena)); // ["dos", "cuatro", "seis"]

Finalmente, mediante la función reduce podemos realizar un cálculo sobre los elementos del array, por ejemplo, podemos contar cuantas veces aparece una ocurrencia dentro de un array o sumar sus elementos. Para ello, la función recibe dos parámetros con el valor anterior y el actual:

var numeros = [1, 3, 5, 7, 9];
var suma = numeros.reduce(function(anterior, actual) {
  return anterior + actual
});

En el primer paso, como no hay valor anterior, se pasan el primer y el segundo elemento del array (los valores 1 y 3). En siguientes iteraciones, el valor anterior es lo que devuelve el código, y el actual es el siguiente elemento del array. De este modo, estamos cogiendo el valor actual y sumándoselo al valor anterior (el total acumulado)

arguments a array

Si queremos convertir el pseudo-array arguments de una función a un array verdadero para poder utilizar sus métodos, podemos hacer uso del prototipo del array, para obtener una copia de los los argumentos:

var args = Array.prototype.slice.call(arguments);

2.10. Destructurar

Una novedad que ofrece ES6 es la posibilidad de destructurar tanto arrays como objetos para transformar una estructura de datos compuesta, tales como objetos y arrays, en diferentes datos individuales. Así pues, en vez de asignar una a una las asignaciones para cada variable mediante múltiples sentencias, al destructurar podemos asignar los valores a múltiples variables en una única sentencia.

En el caso de los arrays, hemos de asignar un array a las variables entre corchetes ([]):

Destructurando de array a variables
var numeros = [10, 20];
var [n1, n2] = numeros;   // destructurando
console.log(n1);          // 10
console.log(n2);

De la misma manera, si queremos extraer los datos de un objeto, lo haremos mediante llaves ({}):

Destructurando de objeto a variables
var posicion = {x: 50, y: 100};
var {x, y} = posicion;           // destructurando
console.log(x);                  // 50
console.log(y);                  // 100

La sintaxis de destructurar también permite emplear su uso como parámetros de las funciones declaración, ya sea un objeto o un array.

Destructurando un objeto como parámetro de una función
persona.saluda(persona2); (1)
persona.saluda({nombre, apellido1}); (2)
1 Recibe un objeto como parámetro, y por tanto dentro de la función saluda accederá a los elementos mediante persona2.nombre y persona2.apellido1
2 Recibe un objeto destructurado como parámetro, y por tanto dentro de la función saluda accederá a los elementos mediante nombre y apellido1 directamente

2.11. Ejercicios

2.11.1. (0.4 ptos) Ejercicio 21. Objeto Usuario

A partir del siguiente objeto el cual se crea mediante una función factoría:

function crearUsuario(usu, pas) {
  return {
    login: usu,
    password: pas,
    autenticar: function(usu, pas) {
      return this.login === usu && this.password == pas;
    }
  };
}

Refactoriza el código utilizando una función constructor que haga uso de descriptores de datos y acceso, de manera que no permita consultar el password una vez creado el objeto Usuario.

Un ejemplo de ejecución sería:

var usuario = new Usuario("l1", "p1");
console.log("login: " + usuario.login); // l1
console.log("password: " + usuario.password); // undefined
usuario.password = "p2";
console.log("password: " + usuario.password); // undefined
console.log("auth? " + usuario.autenticar("l1", "l1")); // false
console.log("auth? " + usuario.autenticar("l1", "p1")); // true

El método autenticar quedará como un método el objeto, es decir, no como una propiedad get/set.

El objeto se almacenará en una archivo denominado ej21.js.

2.11.2. (0.4 ptos) Ejercicio 22. Herencia

Crea un objeto Persona que herede de Usuario, pero que añada atributos para almacenar el nombre y el email.

Al recuperar el nombre de una persona, si no tiene ninguno, debe devolver el login del usuario.

Tanto el objeto Usuario como el objeto Persona se almacenarán en un archivo denominado ej22.js.

2.11.3. (0.3 ptos) Ejercicio 23. String.repetir()

Define una función repetir dentro del objeto String para que, haciendo uso del prototipo, acepte un entero con el número de ocasiones que tiene que repetir la cadena. Por ejemplo:

console.log("Viva JavaScript ".repetir(3));

En el caso de recibir un tipo inesperado o negativo, deberá lanzar un error informativo.

La función se almacenará en una archivo denominado ej23.js.

2.11.4. (0.4 ptos) Ejercicio 24. Arrays

Sin utilizar las instrucciones for ni while, realiza las siguientes funciones:

  1. Función reverseCopia(array) que a partir de un array, devuelva una copia del mismo pero en orden inverso (no se puede utilizar el método reverse)

  2. Función union(array1, array2 [,…​arrayN]) que a partir de un número variable de arrays, devuelva un array con la unión de sus elementos.. Cada elemento sólo debe aparecer una única vez.

Por ejemplo:

var frutas = ["naranja", "pera", "manzana", "uva", "fresa", "kiwi"];
var saturf = miReverse(frutas);

console.log(frutas); // ["naranja", "pera", "manzana", "uva", "fresa", "kiwi"]
console.log(saturf); // ["kiwi", "fresa", "uva", "manzana", "pera", "naranja"]

var zumos = ["piña", "melocotón", "manzana", "naranja"];
var batidos = ["fresa", "coco", "chocolate"];

var sabores = union(frutas, zumos, batidos);
console.log(sabores); // ["naranja", "pera", "manzana", "uva", "fresa", "kiwi", "piña", "melocotón", "coco", "chocolate"]

La funciones se almacenarán en una archivo denominado ej24.js.


1. "Aitor Medrano" y "Bruce Wayne", en el primer console.log(), getNombre() se invoca como un método de obj.prop, por lo que this toma el valor de la propiedad obj.prop.nombre. En el segundo console.log(), al invocarse como función, la variable test forma parte del objeto global, por ello, la función devuelve el valor de la propiedad nombre del objeto window
2. [11, "hola", true, 15, "Bruce", undefined, undefined, undefined, "Wayne"]
3. false, porque tras darle la vuelta los valores son ["Laura, Ana, Pedro, Antonio, Juan"] y al ordenarlos quedan como ["Ana, Antonio, Juan, Laura, Pedro"], por lo que a alfa se asigna el valor Antonio, mientras que nombres[2] queda como Juan