Skip to content

Instantly share code, notes, and snippets.

@bdragon
Created August 7, 2013 22:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bdragon/6179263 to your computer and use it in GitHub Desktop.
Save bdragon/6179263 to your computer and use it in GitHub Desktop.
Inheritance model with support for inheriting static properties, prototype properties, and ECMAScript 5 getters/setters, as well as for calling "super". See http://www.bryandragon.com/articles/playing-with-javascript-inheritance
/**
* Object.extend()
* @author Bryan Dragon
* @website www.bryandragon.com
* @license MIT License
*/
(function () {
'use strict';
var hasOwn = Object.prototype.hasOwnProperty
, descriptorProps =
'get,set,value,writable,enumerable,configurable'.split(',');
// Add the ability to invoke "super" from within the given function
function withSuper(fn, _super) {
return function () {
var callSuper = this.callSuper;
this.callSuper = _super;
var value = fn.apply(this, arguments);
this.callSuper = callSuper;
return value;
};
}
if (typeof Object.extend !== 'function') {
Object.extend = function (Parent, protoProps, staticProps) {
var prototype, Child;
if (typeof Parent !== 'function' && arguments.length < 3) {
staticProps = protoProps;
protoProps = Parent;
Parent = function () {};
}
protoProps || (protoProps = {});
staticProps || (staticProps = {});
Child = function () {
if (this.initialize) {
this.initialize.apply(this, arguments);
}
};
// Inherit static properties
for (var name in Parent) {
if (hasOwn.call(Parent, name)) {
Child[name] = Parent[name];
}
}
// Add static properties
for (var name in staticProps) {
Child[name] = staticProps[name];
}
// Build child prototype
function ctor() { this.constructor = Child; }
ctor.prototype = Parent.prototype;
prototype = new ctor;
// Add instance properties to prototype
for (var name in protoProps) {
// Define ECMAScript5 properties for properties prefixed
// with `$`
if (name.substr(0, 1) === '$' &&
(typeof protoProps[name] === 'object' ||
typeof protoProps[name] === 'function')) {
var descriptor = protoProps[name];
name = name.substr(1);
// Support closures
if (typeof descriptor === 'function') {
descriptor = descriptor();
}
// Allow getter/setter inheritance
var parentDescriptor =
Object.getOwnPropertyDescriptor(Parent.prototype, name);
if (parentDescriptor) {
var hasValue = (hasOwn.call(descriptor, 'value') ||
hasOwn.call(descriptor, 'writable'));
var hasAccessor = (hasOwn.call(descriptor, 'get') ||
hasOwn.call(descriptor, 'set'));
descriptorProps.forEach(function (prop) {
if (prop === 'get' || prop === 'set') {
if (! hasValue) {
if (typeof parentDescriptor[prop] === 'function') {
if (typeof descriptor[prop] === 'function') {
descriptor[prop] =
withSuper(descriptor[prop], parentDescriptor[prop]);
}
else {
descriptor[prop] = parentDescriptor[prop];
}
}
}
}
else if (prop === 'value' || prop === 'writable') {
if (! hasAccessor) {
if (! hasOwn.call(descriptor, prop) &&
hasOwn.call(parentDescriptor, prop)) {
descriptor[prop] = parentDescriptor[prop];
}
}
}
else {
if (! hasOwn.call(descriptor, prop) &&
hasOwn.call(parentDescriptor, prop)) {
descriptor[prop] = parentDescriptor[prop];
}
}
});
}
Object.defineProperty(prototype, name, descriptor);
}
// Allow instance methods to call `this.callSuper()`
else if (typeof protoProps[name] === 'function' &&
typeof prototype[name] === 'function') {
prototype[name] =
withSuper(protoProps[name], Parent.prototype[name]);
}
// Otherwise, just copy the property
else {
prototype[name] = protoProps[name];
}
}
// Apply child prototype
Child.prototype = prototype;
// Provide reference to parent prototype,
// as is convention elsewhere
Child.__super__ = Parent.prototype;
// Allow inheritance directly from child class
Child.extend = function (protoProps, staticProps) {
return Object.extend(Child, protoProps, staticProps);
}
return Child;
};
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment