Last active
December 25, 2015 18:49
-
-
Save joubertnel/7023795 to your computer and use it in GitHub Desktop.
Lazy parse a JavaScript class body
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
<html> | |
<body> | |
<div id="person1"></div> | |
<div id="person2"></div> | |
</body> | |
<script> | |
var util = {}; | |
/** | |
* @class util.Class Provides simple class creation and inheritance | |
* | |
* Based on work from John Resig, base2, and Prototype. Class uses namespace | |
* safe method access allowing renaming of util.Class. | |
* | |
* Create an empty Class: | |
* | |
* var MyEmptyClass = util.Class.create(); | |
* | |
* Create a typical Class instance: | |
* | |
* var MyClass = util.Class.create({ | |
* init: function () { | |
* // This method serves as the constructor | |
* }, | |
* aPrototypeMethod: function () { | |
* // All methods and properties are assigned to MyClass.prototype | |
* } | |
* }); | |
* | |
* Extend a Class instance: | |
* | |
* var YourClass = MyClass.extend({ | |
* init: function () { | |
* // Base class properties are overwritten. Base methods can be invoked | |
* // using the _super() method. | |
* this._super(); | |
* } | |
* }); | |
* | |
*/ | |
(function(){ | |
var _hasSuper = /\b_super\b/, | |
_doNotInit = {}; | |
/** | |
* Enables sub classed methods to call their associated super class method | |
* using `this._super()`. Returns a function which executes in the scope | |
* of a Class instance. | |
* @param | |
* @param | |
* @returns {Function} | |
* @private | |
*/ | |
function _bindSuper (value, superValue) { | |
// A _super() method is bound temporarily (created then destroyed) each | |
// time a subclassed method is run. | |
return function () { | |
var tmp = this._super, | |
ret; | |
this._super = superValue; | |
ret = value.apply(this, arguments); | |
// The method only need to be bound temporarily, so it is removed | |
// after the subclass method has executed. | |
if (tmp) { | |
this._super = tmp; | |
} else { | |
delete this._super; | |
} | |
return ret; | |
}; | |
} | |
/** | |
* Extends a Class instance with properties to create a sub-class. Executes | |
* in scope of a Class instance. | |
* @public | |
* @param {Object} props Object descriptor with key/value pairs | |
* @returns {Function} A Class instance | |
*/ | |
function _extend (props) { | |
var prototype = new this(_doNotInit), | |
name, getter, setter, value | |
; | |
// Copy the properties over onto the new prototype | |
for (name in props) { | |
getter = props.__lookupGetter__(name); | |
setter = props.__lookupSetter__(name); | |
if (getter || setter) { | |
getter && prototype.__defineGetter__(name, getter); | |
setter && prototype.__defineSetter__(name, setter); | |
} else { | |
value = props[name]; | |
if (typeof value === "function" && _hasSuper.test(value)) { | |
// value: sub-class method | |
// this.prototype[name]: this class' (super class) method | |
value = _bindSuper(value, this.prototype[name] || function(){}); | |
} | |
prototype[name] = value; | |
} | |
} | |
return _create(prototype); | |
} | |
/** | |
* Creates a new Class instance, optionally including a prototype | |
* object. This method is not applied to returned Class instances; | |
* use Class.extend to sub-class Class instances. | |
* @public | |
* @param {Object} props Object descriptor with key/value pairs | |
* @returns {Function} A Class instance | |
*/ | |
function _create (props) { | |
var Class = function () { | |
var init = this.init; | |
// All construction is actually done in the init method | |
if (init && arguments[0] !== _doNotInit) { | |
init.apply(this, arguments); | |
} | |
}; | |
// Populate our constructed prototype object | |
if (props) { | |
Class.prototype = props; | |
} | |
// Enforce the constructor to be what we expect | |
Class.constructor = Class; | |
// And make this class extendable | |
Class.extend = _extend; | |
return Class; | |
} | |
// Reveal _class publically | |
util.Class = { | |
create: _create | |
}; | |
}()); | |
function applyConstructor(ctor, args) { | |
var a = []; | |
for (var i = 0; i < args.length; i++) | |
a[i] = 'args[' + i + ']'; | |
return eval('new ctor(' + a.join() + ')'); | |
} | |
Person = function() { | |
var body = "util.Class.create({ init: function(name, address) { this.address = address; this.name = name; }, walk: function() { return 'walked'; } })"; | |
var temp = eval(body), | |
result = applyConstructor(temp, arguments); | |
alert('now memoize - should only see once'); | |
Person = temp; | |
return result; | |
}; | |
/* | |
var Person = util.Class.create({ | |
init: function(name, address) { | |
this.address = address; | |
this.name = name; | |
}, | |
walk: function() { | |
return 'walked'; | |
} | |
}); | |
*/ | |
var person1 = new Person('Sarah', | |
{street: 'blah', talk: function() { alert('boo'); }}); | |
alert(person1.name); | |
alert(person1.address.street); | |
person1.address.talk(); | |
var person1El = document.getElementById('person1'); | |
var person2El = document.getElementById('person2'); | |
person1El.innerHTML = person1.name + ' ' + person1.walk(); | |
var person2 = new Person('Bubba'); | |
person2El.innerHTML = ''; | |
person2El.innerHTML = person2.name + ' ' + person2.walk(); | |
</script> | |
</html> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment