La característica OOP de JavaScript

Por favor consulte:

Una de las dos estructuras principales en JavaScript se llama objeto, la otra es el arreglo. Cuando se crea un objeto literal, por ejemplo:

var point = {x: 0, y: 0};

se usa un tipo de estructura, que se conoce como arreglo asociativo, o diccionario, es probable pero no necesario que se encuentre implementado mediante un “hash map”. OOP es una característica opcional en JavaScript. No hay problema en limitarse al uso de objetos literales, probablemente en conjunto con el patrón de diseño: módulo.

En JavaScript no existe la noción de clase. Más bien se aprovecha un mecanismo diferente, llamado funciones constructoras y prototipos, esto para ofrecer la característica de programación OOP.

Javascript no tiene especificadores de acceso tales como público, privado y protegido. Este es un rasgo que comparte con muchos lenguajes dinámicos que soportan OOP, tal como Python.

Variables

Como un lenguaje orientado a objetos, JavaScript carece de clases. Siempre se debe definir una función constructora. Entonces lo único que se necesita hacer en esta fucnión es nombrar e inicializar los campos o variables miembro que necesitamos para cualquier objeto, por ejemplo:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Las funciones constructoras se comportan como constructoras sí y sólo sí son llamadas con el operador “new”. Por lo tanto, es posible crear un objeto de la siguiente manera:

var p = new Point(0.0, 0.0);

p.x = 42.0;

Cada objeto tiene la propiedad de “buscar en la cadena”, lo cual consiste en objetos enlazados y conocidos como prototipos. Por ejemplo, cuando un trozo de código trata de acceder una propiedad del objeto, tal como en obj.prop, entonces primero se verifica si obj tiene una propiedad llamada prop dentro de sus propias propiedades. Si la tiene, entonces esa propiedad se usa. Si no la tiene, entonces el proceso de “buscar en la cadena” verifica los objetos del prototipo a lo largo de toda la cadena, buscando por una propiedad llamada prop, entonces cuando esa propiedad es encontrada en algun “prototype proto”, el proceso finaliza y el valor proto.prop es usado para obj.prop. De otra manera, obj.prop es indefinido.

Por defecto, en un objeto la propiedad de “buscar en la cadena” consiste de un sólo prototipo, nombrado, Object.prototype, es por esto que los métodos “hasOwnProperty” y “toString” se pueden llamar sobre cualquier objeto. En el caso especial, cuando un objeto es creado con el operador “new”, como en:

var p = new Point(0.0, 0.0);

un objeto prototipo adicional es insertado al inicio de la cadena. Entonces este objeto prototipo es el mismo para todas las instancias del objeto, empieza vacío en lo que concierne a la propiedad de “buscar en la cadena”, y se puede acceder mediante Point.prototype, por lo tanto este el lugar ideal para colocar los métodos del objeto.

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.distance = function(other) {
  return Math.sqrt(Math.pow(this.x - other.x, 2) +
    Math.pow(this.y - other.y, 2));
}

Point.prototype.getLabel = function() {
  return "(" + this.x.toFixed(2) + ", " + this.y.toFixed(2) + ")";
};

En el método getLabel arriba, hay que tener en cuenta que cuando se hace referencia a los campos como x e y dentro de un método, se les tiene que calificar con this.x.toFixed(2). Lo anterior es fácil de olvidar, ya que es opcional en lenguajes basados en clases tradicionales.

A continuación el uso por excelencia:

var p1 = new Point(0.0, 0.0);
var p2 = new Point(3.0, 4.0);

var dist = p2.distance(p1); // dist es 5.0

Para la mayoría de los programadores de aplicaciones en JavaScript, esto es todo lo que hay que saber: el prototipo que es común a todas las instancias de Point se accede a través del constructor, como en Object.prototype.

Como se mencionó anteriormente, los objetos JavaScript que se crean con el constructor de Point a menudo son llamadas instancias del Objeto o instanciaciones de Point. Esto es consistente con el comportamiento del operador instanceof en JavaScript, por ejemplo:

var p = new Point(0.0, 0.0);

p instanceof Point; // true

Campos estáticos

Se caracterizan por dos propiedades:

  • Existen sólo una vez para la clase, son independiente de la existencia de cualquier instanciación.
  • Son accesibles a través del nombre de la clase, en la ausencia de cualquier instanciación.

Sólo hay que colocarlas en el prototipo. Por ejemplo, vamos a decir:

Point.prototype.distanceCounter = 0;
Point.prototype.getLabelCounter = 0;

De esa manera, los contadores se crean una sóla vez, no en función de cada objeto, y será accesible desde el constructor, así como desde un objeto:

alert(Point.prototype.distanceCounter);

var p = new Point(0.0, 0.0);

alert(p.distanceCounter);

En la primera línea del código anterior, el acceso a través del constructor, es la mejor forma de acceder a un campo estático. Pero el acceso a través de un objeto funciona muy bien, debido la propiedad “buscar en la cadena” en el prototipo.

Métodos estáticos

Se caracterizan por las siguientes propiedades:

  • Tienen acceso a los campos estáticos, pero no a los no estáticos.
  • Son accesibles a través del nombre de la clase, en la ausencia de cualquier instanciación.

En JavaScript esto se puede lograr colocando el método en el prototipo, por ejemplo:

Point.prototype.resetCallCounters = function() {
  Point.prototype.distanceCounter = 0;
  Point.prototype.getLabelCounter = 0;
};

Entonces se puede llamar resetCallCounters a través del constructor, en la ausencia de cualquier objeto, así como de un objeto:

Point.prototype.resetCallCounters();

var p = new Point(0.0, 0.0);

p.resetCallCounters();

Una vez más, la primera llamada del código anterior, a través del constructor, debe ser la preferida de usar.

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.distance = function(other) {
  ++Point.prototype.distanceCounter;
  return Math.sqrt(Math.pow(this.x - other.x, 2) +
    Math.pow(this.y - other.y, 2));
}

Point.prototype.getLabel = function() {
  ++Point.prototype.getLabelCounter;
  return "(" + this.x.toFixed(2) + ", " + this.y.toFixed(2) + ")";
};

Point.prototype.distanceCounter = 0;
Point.prototype.getLabelCounter = 0;

Point.prototype.resetCallCounters = function() {
    Point.prototype.distanceCounter = 0;
    Point.prototype.getLabelCounter = 0;
};

Respeto a una disciplina de programación

De hecho, cuando se miran los métodos estáticos y los no estáticos en el ejemplo anterior, se ve que están al lado del objeto prototipo. Lo único que los distingue de los demás es el hecho de que los métodos estáticos tienen acceso a los campos estáticos. No hay nada impida llamar a un método no estático como si fuera estático, por ejemplo:

// Mal uso: método estático llamado como si fuera estático
var label = Point.prototype.getLabel();

Esto no tiene sentido, debido a que el receptor de la llamada de la función es Point.prototype. Por lo tanto, las propiedades x e y de las que se hace referencia en el cuerpo del getLabel serán indefinidas.

Herencia

Supongamos que queremos ampliar las capacidades del objeto Point, dándole una anotación que aparezca cerda de la etiqueta. Esto se puede lograr al derivar una clase, por ejemplo, AnnotatedPoint de Point. Lo siguiente no pretende ser un brillante ejemplo del análisis y el diseño orientado a objetos, pero por el bien de la ilustración, vamos a tener buen sentido del humor. Para ver cómo se logra esto en JavaScript, primero miremos la función constructora para la clase derivada:

function AnnotatedPoint(x, y, annotation) {
  Point.call(this, x, y);
  this.annotation = annotation;
}

La función constructora se llama con el operador “new”, por ejemplo:

var p = new AnnotatedPoint(0.0, 0.0, "Origin");

lo primero que pasa es que un nuevo objeto es creado, y “this” se liga ese objeto. Entonces se corre el cuerpo de la función. Lo primero que debe pasar en el cuerpo de la función es inicializar la instaciación de AnnotatedPoint como una instanciación de Point.

Eso es lo que a función constructora de Point hace: realiza una inicialización que funciona con este lazo hacia el nuevo punto con anotación. Por esta razón es que es importante que las funciones constructoras no sólo sean sean llamadas con el operador “new”, que construye un nuevo objeto, también como funciones ordinarias, con un lazo a un objeto existente. El resto del cuerpo de la función AnnotatedPoint es obvio: crea e inicializa lo campos adicionales de la anotación.

Hasta ahora hemos logrado que la anotación de un punto herede todas las propiedades de la instancia, eso mismo, todos los campos no estáticos de un punto. Seguidamente, se necesita asegurar que los puntos con anotación si heredan los métodos y campos estáticos de punto, eso es, las cosas que son guardadas en Point.prototype. Esto se logra insertando Point.prototype en la propiedad de “buscar en la cadena” para los puntos con anotación.

La implementación completa de AnnotatedPoint en JavaScript es:

//
// The equivalent of the AnnotatedPoint class in JavaScript
//

function AnnotatedPoint(x, y, annotation) {
  Point.call(this, x, y);
  this.annotation = annotation;
}

// ES5 only
AnnotatedPoint.prototype = Object.create(Point.prototype);

AnnotatedPoint.prototype.getLabel = function() {
  var pointLabel = Point.prototype.getLabel.call(this);
  return pointLabel + " " + this.annotation;
};

En programación orientada a objetos

Una relación de herencia constituye una relación “es-un”: donde cada punto con anotación es un punto. El operador instanceof en JavaScript reconoce que:

var ap = new AnnotatedPoint(0.0, 0.0, "Origin");

ap instanceof AnnotatedPoint; //true
ap instanceof Point; //true

Bash it mash up

Por favor consulte: “Bash it mash up

Clonar en nuestro “home”

git clone https://github.com/revans/bash-it.git ~/.bash_it

Correr la instalación

~/.bash_it/install.sh

Editar al gusto

vim ~/.bash_profile

Se puede deshabilitar “plugins”

bash-it disable plugin chruby-auto
bash-it disable plugin chruby
bash-it disable plugin java
bash-it disable plugin jekyll
bash-it disable plugin latex
bash-it disable plugin nginx
bash-it disable plugin osx
bash-it disable plugin z_autoenv
bash-it disable plugin z

Se puede deshabilitar “aliases”

bash-it disable alias bundler
bash-it disable alias emacs
bash-it disable alias heroku
bash-it disable alias hg
bash-it disable alias homebrew
bash-it disable alias jitsu
bash-it disable alias maven
bash-it disable alias osx
bash-it disable alias rails
bash-it disable alias textmate