1.0.4 • Published 5 years ago

extend-inherit v1.0.4

Weekly downloads
-
License
ISC
Repository
github
Last release
5 years ago

extend-inherit

Simple extension/inheritance mechanism for JavaScript constructor functions.

Overview

There are two ways of using it:

  • BaseClass.extend(SubClass)
      const Car = Object.extend(function (brand) {
        this.getBrand = () => brand;
      });
  • SubClass.inheritFrom(BaseClass)
      const Car = function (brand) {
        this.getBrand = () => brand;
      }.inheritFrom(Object);    

API Usage - More examples

To learn more details on the API usage just have a look at the spec files for extend and inheritFrom.

Background and details

I like to define my classes using constructor functions and want a nice extension mechanism using plain-old JavaScript. I prefer this notation over ES6 classes and prototype notation since only this supports clean encapsulation. Unfortunately, most existing libraries provide an extends functionality that only support property maps which then again translates into prototype notation and doesn't allow for proper encapsulation.

Consider:

  const Car = function (brand) {
    this.getDisplayName = () => {
      return brand;
    };
  };

  const toyota = new Car('Toyota');
  toyota.getBrand(); //returns 'Toyota'
  toyota.brand; //returns undefined as 'brand' is properly encapsulated by the constructor closure

  const VintageCar = Car.extend(function (brand, yearOfConstruction) {
      Car.call(this, brand);
      const superGetBrand = this.getBrand;
      this.getBrand = () => `${superGetBrand()} (${yearOfConstruction})`;
  });

  const toyota1960 = new VintageCar('Toyota', 1960);
  toyota1960.getBrand(); //'Toyota (1960)'

  toyota1960 instanceof VintageCar; //true
  toyota1960 instanceof Object //true
  toyota1960 instanceof Car //true

Compare to prototype notation and prototype-based extension libs:

  const Car = function (brand) {
    this._brand = brand; //underscore typically indicates it's private, but it's still visible - and doesn't look nice
  };
  Car.prototype.getBrand = function() {
    return this._brand;
  };

  const toyota = new Car('Toyota');
  toyota.getBrand(); //returns 'Toyota'
  toyota._brand; //returns 'Toyota' - implementation detail is exposed to the outside

  // Below is often how prototype - based extension libs work:
  // You pass in a set of methods which typically get assigned to the prototype.
  // The downside also here that there is no way of having clean encapsulation.
  Car.extend('VintageCar', {
    constructor : function(brand, yearOfConstruction) {
      Car.call(this, brand);
      this._yearOfConstruction = yearOfConstruction;
    },
    getBrand : function() {
      const superBrand = Car.prototype.getBrand.call(this);
      return `${superBrand} (${this._yearOfConstruction})`;
    }
  });

Implementation

Super simple. The basic mechanism is described very well on the Mozilla pages. I just gave it a little twist and added the extend/inheritFrom logic to Function.prototype, making it conveniently accessible on all functions.

const assignPrototype = function(BaseClass, SubClass) {
  SubClass.prototype = Object.create(BaseClass.prototype);

  Object.defineProperty(SubClass.prototype, 'constructor', {
    value: SubClass,
    enumerable: false,
    writable: true
  });
};

Function.prototype.extend = function(SubClass) {
  assignPrototype(this, SubClass);
  return SubClass;
};

Function.prototype.inheritFrom = function(BaseClass) {
  assignPrototype(BaseClass, this);
  return this;
};
1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago