Objetivos

  • Al finalizar la unidad tendrás una idea clara de las peculiaridades de javascript como lenguaje de programación orientado a objetos. Sabrás como se crean los objetos directamente mediante literales o automáticamente mediante funciones constructoras. Aprenderás a desarrollar un estilo de programación orientada a objetos sin clases, basado en estas funciones constructoras. Y comprenderás el importante y potente concepto de prototipo, el cual nos permite implementar las ideas de la herencia en un lenguaje que no dispone de clases.

Videos de la unidad

A lo largo de la unidad se irán presentando diferentes videos. Aquí te ofrecemos la lista de reproducción con todos los videos de la unidad, para que los tengas más a mano.

Objetos planos de javascript

Los objetos son elementos fundamentales en la programación con javascript. Más allá de los tipos básicos number, string y boolean, todo lo demás es un objeto. Por ejemplo los arrays y las propias funciones son objetos.

Un objeto no es más que una colección de propiedades. Cada propiedad tiene asociada un valor que puede ser cualquier cosa, incluido otro objeto. Por ello podemos crear objetos que representen prácticamente cualquier tipo de estructura: árboles, redes, listas, etcétera.

Hay dos maneras de crear objetos en javascript. La primera es directamente mediante literales:

let jupiter = {
    masa: 1.899e27,
    radio: 778412026
}

En muchas ocasiones es suficiente e incluso beneficioso crear objetos directamente de esta manera. Pero en otras, especialmente cuando nos apoyamos más en un estilo orientado a objetos, necesitamos algún tipo de plantilla a partir de la cual construir (instanciar) nuevos objetos. En los lenguajes de programación orientados a objetos basados en clases (Java, C++, Python) esta plantilla es la clase.

Pero resulta que en javascript NO hay clases. Sin embargo podemos crear nuevos objetos de manera automática utilizando funciones constructoras. Estas no son más que funciones de javascript que se invocan con el operador new:

function Planeta(masa, radio){
    this.masa = masa;
    this.radio = radio;
}

let jupiter = new Planeta(1.899e27, 778412026);
El estándar ES6, que será objeto de estudio de la unidad 5, introduce como syntactic sugar, es decir, como conveniencia sintáctica, la declaración de clases al estilo tradicional. Sin embargo, internamente, el intérprete de javascript sigue usando funciones constructoras para la instanciación de los objetos creados con dichas clases.

Cuando usamos new al invocar una función, se crea un nuevo objeto vacío y se establece como referencia del parámetro implícito this de la función. Así podemos ir construyendo dicho objeto a medida que la función se ejecuta. Una vez finalizada la ejecución, la expresion de la parte derecha de la asignación anterior, devuelve el objeto construido por la función.

Así pues, las funciones constructoras, aunque no son clases, podemos decir que actúan como tales.

Todo esto lo explicamos con más detalle en el siguiente vídeo.

Descriptores de propiedades

Las propiedades de los objetos presentan un comportamiento por defecto que consiste en que pueden ser reescritas, enumeradas en un bucle for ... in y reconfiguradas. Pero, si lo necesitamos, todo esto puede ser cambiado. Para ello tenemos que cambiar el descriptor de la propiedad, lo cual podemos hacer usando la función Object.defineProperty().

Este vídeo te muestra como hacerlo:

Herencia con prototipos

Todos los objetos de javascript, incluso el más básico que podemos crear mediante el literal {}, tiene una propiedad especial que se llama prototipo. Esta propiedad es un objeto que, por defecto, contiene una serie de propiedades como, por ejemplo, la función toString().

Lo interesante y especial del prototipo es que cuando pedimos al objeto una propiedad que no forma parte directamente de él, es decir, que no la hemos añadido cuando creamos el objeto, el intérprete de javascript busca en el prototipo del objeto dicha propiedad, y si la encuentra la devuelve como si fuera una propiedad del objeto.

Las propiedades que añadimos a un objeto cuando lo creamos se denominan propiedades propias del objeto (own properties). Dada una propiedad del objeto, podemos saber si es propia o adquirida a través del prototipo usando la función hasOwnProperty() que todos los objetos tienen, precisamente a través de sus prototipos.

Por ello un objeto, además de tener las propiedades que le corresponde en el momento de su creación, tiene también propiedades a través de su prototipo. Y este mecanismo es la base para la implementación de la herencia en javascript.

En los vídeos que siguen explicamos en detalle qué es el prototipo y como usarlo para implementar la herencia con javascript.

En este primer vídeo sobre el prototipo explicamos con detalle qué es y como se puede modificar para construir la cadena de prototipos, que esencialmente es un prototipo que tiene entre sus propiedades otro prototipo que, a su vez, puede tener otro prototipo y así hasta que se llegue a un prototipo final.
En este segundo vídeo se trata el tema del prototipo en funciones constructoras.
La idea de prototipo es muy potente pero a la vez puede resultar muy extraña y novedosa para el que se enfrenta por primera vez a ella. En este vídeo mostramos algunas indicaciones y observaciones que conviene tener en cuenta cuando se usa el prototipo.

Controlando el acceso a los objetos (getters y setters)

En muchos casos necesitamos controlar el acceso a las propiedades de objetos, tanto cuando las obtenemos como cuando les asignamos un valor. Puede ser que necesitemos realizar una validación antes de realizar la asignación, o alguna transformación, o simplemente queramos dejar constancia del acceso en algún tipo de registro. También puede ocurrir que queramos proteger el acceso directo a ciertas propiedades, es decir hacerlas privadas al objeto.

El siguiente vídeo ofrece la solución a estos problemas. En él se introduce una estrategia basada en clausuras para proteger las propiedades de un objeto y los conceptos de getters y setters de javascript con los que mejoramos el poder semántico del acceso a funciones que se comportan, en lo que al acceso se refiere, como si fueran atributos simples.

Qué hemos aprendido

  • Al no disponer de clases, la programación a objetos en javascript es muy diferente a la programación a objetos clásica.
  • Los objetos se pueden crear inmediatamente con literales o automáticamente con funciones constructoras, que no son más que funciones que se invocan con el operador new y que reciben un objeto vacío como referencia de su parámetro implícito this.
  • Las propiedades de los objetos se pueden reconfigurar con la función Object.defineProperty().
  • El prototipo es un objeto que todos los objetos tienen como propiedad. Las propiedades del prototipo pueden considerarse como propiedades de su objeto, ya que si este último no tiene la propiedad que le pedimos el intérprete de javascript la busca en el prototipo y, si la encuentra, la devuelve como si fuera del objeto.
  • El prototipo, como cualquier otra propiedad de un objeto, puede ser modificado. Para hacer esto usamos la función Object.setPrototypeOf(). Este hecho da la clave para la implementación del concepto de herencia.
  • Las funciones, como objetos que son, tienen también su prototipo. Pero además tienen una propiedad, denominada prototype, que no debemos confundir con el prototipo de la función, y que es utilizada como refefencia para el objeto prototipo de todos los objetos que se creen con la función.
  • Los prototipos pueden modificarse de manera que ellos mismos tengan otro prototipo. De esta manera podemos construir lo que se conoce como cadena de prototipos la cual, al ser asignada a un objeto, lo dota de todas las propiedades que tenga cada uno de los prototipos que forma la cadena.
  • Si necesitamos controlar el acceso a las propiedades de un objeto, podemos usar los getter y setter del objeto, que son funciones con una semántica de atributo o propiedad simple, es decir, no necesitamos usar los paréntesis para invocarla y, en el caso del setter, usamos la forma de asignación normal (con signo =) de una propiedad.
  • Para proteger el acceso a ciertas variables de un objeto, como hacen los lenguajes orientados a objeto clásicos con las propiedades privadas, podemos usar el concepto de clausura.