Last active
August 29, 2015 14:08
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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