Compartir métodos entre diferentes objetos

Esta es una alternativa que tenemos para emular herencia en JavaScript. Lo que hacemos acá es simplemente compartir los métodos y propiedades de un objeto en otro.

// Constructor Base
function Shape () {
  return {
    type: this.constructor.name,
    sides: []
  }
}

function Rectangle(width, height) {
  // Instancia del Constructor Base
  var shape = Shape.call(this);

  // Establecer propiedades
  shape.sides.push(width, height, width, height);
  shape.getArea = function() {
    return shape.sides[0] * shape.sides[1];
  }

  return shape;
}

function Square(width) {
  // Instancia del constructor Rectangle
  var rectangle = Rectangle.apply(this, [width, width]);

  return rectangle;
}

var myRectangle = new Rectangle(5, 7);
console.log(myRectangle.type, myRectangle.getArea()); // Rectangle 35

var mySquare = new Square(5);
console.log(mySquare.type, mySquare.getArea()); // Square 25

Es una manera simple de compartir código, pero realmente lo que está pasando es que cada instancia del constructor Rectangle tiene su propia copia del método getArea, jamás se heredan por medio de la cadena de prototype.

Además todos los objetos son instancias de Object, no de los constructores como tal.

console.log(mySquare.hasOwnProperty('getArea')); // true
console.log(mySquare instanceof Square); // false
console.log(mySquare instanceof Object); // true

Herencia prototypal

Es la manera estándar que ofrece JavaScript para herencia de propiedades y métodos. Debido a esto se dice que JavaScript es un lenguaje orientado a Prototipos.

// Constructor Base
function Shape() {}

// Definición de métodos en prototype de Constructor Base
Shape.prototype.getArea = function() {
  return this.sides[0] * this.sides[1];
}

function Rectangle(width, height) {
  this.sides = [width, height, width, height];
}

// Heredar del prototype de Shape sus métodos y propiedades
Rectangle.prototype = new Shape();
// Mantener constructor
Rectangle.prototype.constructor = Rectangle;

function Square(width) {
  this.sides = [width, width, width, width];
}

Square.prototype = new Shape();
Square.prototype.constructor = Square;

var myRectangle = new Rectangle(5, 7);
console.log(myRectangle.constructor.name, myRectangle.getArea()); // Rectangle 35

var mySquare = new Square(5);
console.log(mySquare.constructor.name, mySquare.getArea()); // Square 25
 

La gran diferencia acá es que el método getArea quedó definido en el prototype del constructor base Shape y este se va heredando actualizando el prototype de Rectangle y de Square. Además todos los objetos son instancias de sus respectivos constructores:

console.log(mySquare.hasOwnProperty('getArea')); // false
console.log(mySquare instanceof Square); // true

También es posible sobreescribir un método de una manera diferente agregandolo como una propiedad “propia” del objeto en el constructor:

function Triangle(base, height) {
  this.sides = [base, height];
  // Sobreescribir el método getArea
  this.getArea = function() {
    return .5 * this.sides[0] * this.sides[1];
  }
}

Triangle.prototype = new Shape();
Triangle.prototype.constructor = Triangle;

var myTriangle = new Triangle(4, 10);

console.log(myTriangle.constructor.name, myTriangle.getArea()); // Triangle 20
console.log(myTriangle.hasOwnProperty('getArea')); // true