Skip to content

Instantly share code, notes, and snippets.

@dalgard
Last active August 29, 2015 13:56
Show Gist options
  • Save dalgard/9169923 to your computer and use it in GitHub Desktop.
Save dalgard/9169923 to your computer and use it in GitHub Desktop.
Extensible constructor pattern (ES5 version) inspired by Backbone.js – NOTE: Using `this.super` multiple times in the inheritance chain creates a problem since it results in a loop. There hasn't yet been an elegant solution to this, but a proper `super` keyword will be supported in ES6. In the meantime, coupling with the super's name can be avoi…
function Class(properties) {
// This base constructor can be left empty, but a nice boilerplate might look like this
if (properties) {
// Add properties to the instance
Object.getOwnPropertyNames(properties).forEach(function (property_name) {
var descriptor = Object.getOwnPropertyDescriptor(properties, property_name);
Object.defineProperty(this, property_name, descriptor);
}, this);
}
}
// Extensible constructors pattern inspired by Backbone.js
Class.extend = function (prototype) {
var constructor;
// Use constructor function from passed in prototype or from superclass
if (prototype && prototype.hasOwnProperty("constructor") && typeof prototype.constructor === "function") {
constructor = prototype.constructor;
delete prototype.constructor; // This property is re-attached later
}
else {
var self = this;
constructor = function Subclass() { return self.apply(this, arguments); };
}
// Set up the prototype chain
constructor.prototype = Object.create(this.prototype);
// Locked down references
Object.defineProperties(constructor.prototype, {
"constructor": { value: constructor },
"super": { value: this.prototype }
});
if (prototype) {
// Add extended prototype properties to the subclass
Object.getOwnPropertyNames(prototype).forEach(function (property_name) {
var descriptor = Object.getOwnPropertyDescriptor(prototype, property_name);
Object.defineProperty(constructor.prototype, property_name, descriptor);
});
}
// Add extend capability and reference to super constructor
constructor.extend = this.extend;
constructor.super = this;
return constructor;
};
@dalgard
Copy link
Author

dalgard commented Feb 23, 2014

Attempt at making a class pattern that meets the following demands:

  • Correct prototype chain (incl. working instanceof)
  • Easily extensible ad infinitum
  • Uses constructor/initializer function (possibly inherited)
  • Access to constructor and superclass
  • Use as much native syntax as possible
  • Useful names in devtools [only successful for named, non-inherited constructor functions]

@dalgard
Copy link
Author

dalgard commented Feb 23, 2014

/* Example */

var Animal = Class.extend({
  constructor: function Animal(name) {  // Named function for better debugging
    if (typeof name === "string") console.log("I'm " + name + "!");
    else console.log("I am an animal");
  },
  eats: true
});

var Cat = Animal.extend({
  furry: true
});

var miffy = new Cat("Miffy");  // "I'm Miffy!"

// Beware that the values of these properties are shared by instances
miffy.eats;  // true
miffy.furry;  // true

miffy instanceof Cat;  // true
miffy instanceof Animal;  // true

@dalgard
Copy link
Author

dalgard commented Feb 23, 2014

/* ... continued from above */

var Mammal = Animal.extend({
  constructor: function Mammal() {
    // Instance field
    this.endothermic_amniote = true;

    console.log("Mammal, Ma'am");

    // Also call super's constructor function in this context
    this.super.constructor.apply(this, arguments);
  }
});

var my_cow = new Mammal("mootable");  // "Mammal, Ma'am" "I'm mootable!"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment