Created
November 18, 2011 05:00
-
-
Save munro/1375645 to your computer and use it in GitHub Desktop.
JavaScript Class Abstraction
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
// Class sugar | |
var Class = (function () { | |
'use strict'; | |
// Base class | |
function Class() { | |
throw new Error('Cannot instantiate `Class`.'); | |
} | |
// Copies another object's prototype into ours, skipping the `initialize` | |
// method. Giving the class control over the constructor, from there | |
// the parent class and mixin constructors can be called with. | |
function makeMixin(WrappedClass) { | |
return function (properties) { | |
var key; | |
for (key in properties) { | |
if (properties.hasOwnProperty(key) && key != 'initialize') { | |
WrappedClass.prototype[key] = properties[key]; | |
} | |
} | |
}; | |
} | |
// Wrap prototype method by using the self variable to bind the context | |
// of the method, then slicing self from the arguments. | |
function wrapPrototypeMethod(fn) { | |
return function (self) { | |
var args = Array.prototype.slice.call(arguments, 1); | |
fn.apply(self, args); | |
}; | |
} | |
// Inheritance sugar for extending our class. It binds all of the | |
// prototypal methods' context to our new object. | |
function makeExtend(class_obj) { | |
return function (properties) { | |
var key; | |
function WrappedClass() { | |
var obj, key; | |
// Create new object if the `new` keyword was not used. Check | |
// against `global` for Node.js, and `window` for browser side | |
// JavaScript. | |
if (this === (typeof window === 'undefined' ? global : window)) { | |
obj = Object.create(WrappedClass.prototype); | |
} else { | |
obj = this; | |
} | |
// Set __super__ as the parent's prototype, because the user | |
// will want direct access to the methods for convenience. | |
obj.__super__ = WrappedClass.__super__.prototype; | |
// Copy methods & bind all methods to our new object. | |
for (key in WrappedClass.prototype) { | |
if (typeof WrappedClass.prototype[key] === 'function') { | |
obj[key] = Class.bind(obj, WrappedClass.prototype[key]); | |
} | |
} | |
// Call the constructor | |
if (typeof obj.initialize === 'function') { | |
obj.initialize(); | |
} | |
// Return the constructed object if `new` keyword was not used. | |
return obj; | |
} | |
WrappedClass.__super__ = class_obj; | |
WrappedClass.extend = makeExtend(WrappedClass); | |
WrappedClass.mixin = makeMixin(WrappedClass); | |
// Mixin the passed properties. This is done because the passed | |
// properties aren't inheriting the parent class' prototype, but | |
// they need to be. | |
WrappedClass.prototype = Object.create(class_obj.prototype); | |
WrappedClass.mixin(properties); | |
if (properties.initialize) { | |
WrappedClass.prototype.initialize = properties.initialize; | |
} | |
return WrappedClass; | |
}; | |
} | |
// Manually create the base class | |
Class.__super__ = Object; | |
Class.extend = function (arg1, arg2) { | |
var _extend = makeExtend(Class); | |
// Extend base class | |
if (typeof arg2 === 'undefined') { | |
return _extend(arg1); | |
// Convenience function for extending a prototype | |
} else { | |
return Class.create(arg1).extend(arg2); | |
} | |
}; | |
Class.mixin = makeMixin(Class); | |
Class.bind = function (obj, fn) { | |
return function () { | |
var args = Array.prototype.slice.call(arguments, 0); | |
args.splice(0, 0, obj); | |
return fn.apply(obj, args); | |
}; | |
}; | |
// Wrap prototype to mimic a class. TODO: cache the wrapped | |
// prototype. | |
Class.create = function (prototype) { | |
var key; | |
function WrappedPrototype() { | |
} | |
WrappedPrototype.prototype = Object.create(prototype); | |
for(key in prototype.prototype) { | |
if (typeof prototype.prototype[key] === 'function') { | |
WrappedPrototype.prototype[key] = | |
wrapPrototypeMethod(prototype.prototype[key]); | |
} | |
} | |
return makeExtend(WrappedPrototype); | |
}; | |
return Class; | |
}()); | |
// Testing | |
(function () { | |
'use strict'; | |
var Foo, Bar, Mixin, ProtoExtend, foo, bar, proto; | |
Foo = Class.extend({ | |
_name: 'foo-no-name', | |
initialize: function (self) { | |
console.log('Initializing Foo:', self.name()); | |
}, | |
foo: function (self, a, b, c) { | |
console.log('Wee', a, b, c); | |
}, | |
name: function (self, name) { | |
if (typeof name !== 'undefined') { | |
self._name = name; | |
} | |
return self._name; | |
}, | |
check_this_out: function (self) { | |
console.log('Called from Foo:', self._name); | |
} | |
}); | |
Mixin = Class.extend({ | |
initialize: function (self) { | |
console.log('Initializing mixin:', self.name()); | |
}, | |
mixed_in_method: function (self) { | |
console.log('I was mixed in:', self.name()); | |
} | |
}); | |
Bar = Foo.extend({ | |
_name: 'bar-no-name', | |
initialize: function (self) { | |
self.__super__.initialize.call(self, self); | |
console.log('Initializing Bar:', self.name()); | |
Mixin.prototype.initialize.call(self, self); | |
}, | |
bar: function (self, rawr) { | |
console.log('BAR', rawr); | |
}, | |
onEvent: function (self) { | |
console.log('Event triggered:', self.name()); | |
}, | |
check_this_out: function (self) { | |
self.__super__.check_this_out.call(self, self); | |
console.log('Called from Bar:', self._name); | |
} | |
}); | |
Bar.mixin(Mixin.prototype); | |
function Prototype() { | |
console.log('Initializing Prototype', this._name); | |
} | |
Prototype.prototype.prototypal_method = function (a, b, c) { | |
console.log('Prototype.prototypal_method: ', a, b, c); | |
}; | |
ProtoExtend = Class.extend(Prototype, { | |
_name: 'proto-extend-no-name', | |
initialize: function (self) { | |
console.log('Initializing ProtoExtend'); | |
} | |
}); | |
proto = ProtoExtend(); | |
proto.prototypal_method(1, 2, 3, 4); | |
foo = Foo(); // No more `new` keyword! yay! | |
bar = new Bar(); | |
foo.name('foo'); | |
bar.name('bar'); | |
console.log(foo.name() + ', and ' + bar.name()); | |
foo.check_this_out(); | |
bar.check_this_out(); | |
bar.mixed_in_method(); | |
setTimeout(bar.onEvent, 5); | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment