extend-inherit v1.0.4
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;
};