Skip to content

Instantly share code, notes, and snippets.

@gingi
Last active August 29, 2015 14:08
Show Gist options
  • Save gingi/c9aad349922e4f597b3e to your computer and use it in GitHub Desktop.
Save gingi/c9aad349922e4f597b3e to your computer and use it in GitHub Desktop.
A method for generically inheriting classes. Supports strict mode, and the ability to instantiate objects with and without the `new` keyword. Combines ideas from Douglas Crockford and John Resig.
/**
* @method inherit
*
* Creates a new Class. Implements the necessary wiring to ensure a valid
* prototype chain. Returns an object constructor which can be used to
* instantiate new objects. The method accepts two optional parameters: (1) a
* parent class from which to inherit, and (2) a set of methods. An
* `initialize` method can be specified which will be called automatically upon
* object instantiation.
*
* **Example.** Showing the different usages of `inherit`.
*
* @example
* // No arguments, assumes `Object` as parent class
* var SuperClass = inherit();
*
* // Using both arguments
* var SubClass1 = inherit(SuperClass, {
* initialize: function (value) { this.foo = value },
* method: function () { return "Hello"; }
* });
*
* // Using just class argument
* var SubClass2 = inherit(SuperClass);
*
* var object1 = new SubClass1("bar");
* // object1.foo == "bar"
*
* var object2 = new SubClass2();
* // object2 instanceof "SuperClass"
*
* @param {Function} [parent=Object] The parent object to inherit from
* @param {Object} [methods] Methods to add to the new child class
* @param {Function} [methods.initialize] A special initialize method called
* when an object is created (distingished from the native
* constructor).
*
* @return {Function} The subclass constructor
* @return {Function} return._super Shorthand reference to the superclass
*/
function inherit(Parent, methods) {
if (typeof Parent === "object" && !methods) {
methods = Parent;
Parent = Object;
}
methods = (methods || {});
var Child, prototype = Object.create(Parent && Parent.prototype);
for (var key in methods) {
if (methods.hasOwnProperty(key)) {
prototype[key] = methods[key];
}
}
var initializer = methods.initialize || prototype.initialize;
Child = function _wrapper(args) {
var obj;
if (this instanceof _wrapper) {
obj = this;
} else {
_surrogate.prototype = _wrapper.prototype;
obj = new _surrogate();
}
if (typeof initializer === "function")
initializer.apply(obj, arguments);
return obj;
};
function _surrogate() {}
Child.prototype = prototype;
Child._super = Parent.prototype;
prototype.constructor = Child;
return Child;
}
// Uses Mocha and Should.js
describe("inherit", function () {
it("should allow creation of a basic object with methods", function () {
var A = inherit(Object, {
foo: function () { return "foo" }
});
var a = A();
a.foo().should.equal("foo");
});
it("should assume Object when only one object argument", function () {
var A = inherit({
method: function () { return 52; }
});
var a = A()
a.method().should.equal(52);
a.should.be.instanceOf(Object);
});
it("should allow extending a class with no args", function () {
var A = inherit({
initialize: function () { this.cheese = "Chevre"; }
});
var B = inherit(A);
B().cheese.should.equal("Chevre");
})
it("should allow extending one object to another", function () {
var count = 0;
var A = inherit(Object, {
initialize: function () { this.aVar = "foo"; },
action: function () { return "X"; },
inc: function () { count++; }
});
var B = inherit(A, {
bVar: "bar",
action: function () { return "Y"; },
inc: function () {
B._super.inc.apply(this);
count++;
}
});
var a = A();
var b = B();
a.should.be.an.instanceOf(A);
a.should.not.be.an.instanceOf(B);
b.should.be.an.instanceOf(B);
b.should.be.an.instanceOf(A);
a.aVar.should.equal("foo");
b.aVar.should.equal("foo");
b.bVar.should.equal("bar");
(a.bVar === undefined).should.be.true;
a.action().should.equal("X");
b.action().should.equal("Y");
a.inc(); count.should.equal(1);
b.inc(); count.should.equal(3);
});
it("should allow overriding of constructor", function () {
var x;
var A = function (inputA) { x = inputA; };
var B = inherit(A, {
initialize: function (inputA, inputB) { x = inputB; }
});
var a = A("foo");
x.should.equal("foo");
var b = B("foo", "bar");
x.should.equal("bar");
});
it("should allow calling of super's constructor", function () {
var constructorCalled = false;
var A = inherit(Object, {
initialize: function () { constructorCalled = true; },
foo: function () { return "X" }
});
var B = inherit(A, {});
var b = B();
constructorCalled.should.be.true;
b.foo().should.equal("X");
constructorCalled = false;
var C = inherit(A, {
initialize: function () { A.call(this); },
foo: function () { return "Y"; }
});
var c = C();
constructorCalled.should.be.true;
c.foo().should.equal("Y");
});
it("should support `new` usage", function () {
var A = inherit(Object, {
initialize: function (key1, key2) {
this.key1 = key1;
this.key2 = key2;
}
});
var a = new A("foo", "bar");
a.should.be.instanceOf(A);
a.key1.should.equal("foo");
a.key2.should.equal("bar");
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment